update oauth

This commit is contained in:
lendry
2026-06-25 14:40:05 +03:00
parent 1796008a28
commit 6c63343fc7
19 changed files with 623 additions and 200 deletions

View File

@@ -0,0 +1,134 @@
'use client';
import { Copy, ExternalLink, KeyRound, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { OAuthClient } from '@/lib/api';
import { OAuthEndpoints, buildAuthorizeUrl } from '@/lib/oauth-url';
function CopyRow({ label, value, onCopy }: { label: string; value: string; onCopy: (value: string, label: string) => void }) {
return (
<div className="rounded-2xl bg-[#f4f5f8] p-4 text-sm">
<div className="mb-2 flex items-center justify-between gap-2">
<span className="font-medium">{label}</span>
<Button variant="ghost" size="icon" aria-label={`Скопировать ${label}`} onClick={() => onCopy(value, label)}>
<Copy className="h-4 w-4" />
</Button>
</div>
<code className="block break-all text-xs">{value}</code>
</div>
);
}
export function OAuthClientDetailDialog({
client,
endpoints,
open,
onOpenChange,
onCopy,
onRotateSecret,
onToggleActive
}: {
client: OAuthClient | null;
endpoints: OAuthEndpoints;
open: boolean;
onOpenChange: (open: boolean) => void;
onCopy: (value: string, label: string) => void;
onRotateSecret: (clientId: string) => void;
onToggleActive: (client: OAuthClient) => void;
}) {
if (!client) return null;
const primaryRedirect = client.redirectUris[0] ?? 'https://app.example.com/oauth/callback';
const scope = client.scopes.join(' ');
const sampleAuthorizeUrl = buildAuthorizeUrl(endpoints.issuer, {
clientId: client.clientId,
redirectUri: primaryRedirect,
scope
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>{client.name}</DialogTitle>
</DialogHeader>
<div className="space-y-4 text-sm">
<div className="flex flex-wrap gap-2">
<span className="rounded-full bg-[#eef4ff] px-3 py-1 text-xs font-medium text-[#1d4ed8]">
{client.type === 'PUBLIC' ? 'Public' : 'Confidential'}
</span>
<span className={`rounded-full px-3 py-1 text-xs font-medium ${client.isActive ? 'bg-emerald-50 text-emerald-700' : 'bg-zinc-100 text-zinc-600'}`}>
{client.isActive ? 'Активно' : 'Отключено'}
</span>
{client.createdByDisplayName ? (
<span className="rounded-full bg-zinc-100 px-3 py-1 text-xs text-zinc-600">Создал: {client.createdByDisplayName}</span>
) : null}
</div>
<section className="space-y-3">
<h3 className="font-medium">Данные клиента</h3>
<CopyRow label="Client ID" value={client.clientId} onCopy={onCopy} />
{client.type !== 'PUBLIC' ? (
<p className="text-xs text-[#667085]">
Client Secret выдаётся один раз при создании или после «Новый secret». Храните только на backend.
</p>
) : null}
<CopyRow label="Redirect URI" value={client.redirectUris.join('\n')} onCopy={onCopy} />
<CopyRow label="Scopes" value={scope} onCopy={onCopy} />
</section>
<section className="space-y-3">
<h3 className="font-medium">Подключение OAuth 2.0 / OpenID Connect</h3>
<p className="text-xs text-[#667085]">
Issuer и endpoints берутся из настройки <strong>PUBLIC_API_URL</strong> в админке. В стороннем сервисе укажите issuer = базовый URL API, не authorization endpoint.
</p>
<CopyRow label="Issuer (PUBLIC_API_URL)" value={endpoints.issuer} onCopy={onCopy} />
<CopyRow label="Authorization endpoint" value={endpoints.authorizationEndpoint} onCopy={onCopy} />
<CopyRow label="Token endpoint" value={endpoints.tokenEndpoint} onCopy={onCopy} />
<CopyRow label="UserInfo endpoint" value={endpoints.userInfoEndpoint} onCopy={onCopy} />
<CopyRow label="OIDC Discovery" value={endpoints.openIdConfigurationUrl} onCopy={onCopy} />
<CopyRow label="Пример authorize URL" value={sampleAuthorizeUrl} onCopy={onCopy} />
</section>
<section className="rounded-2xl border border-[#eceef4] p-4">
<div className="mb-2 flex items-center gap-2 font-medium">
<KeyRound className="h-4 w-4" />
Быстрый старт
</div>
<ol className="list-decimal space-y-1 pl-5 text-xs text-[#667085]">
<li>Авторизуйте пользователя в IdP и получите userId.</li>
<li>Перенаправьте на authorization endpoint с clientId, redirectUri, scope, userId.</li>
<li>На backend обменяйте code через POST /oauth/token с client_secret.</li>
<li>Запросите профиль через GET /oauth/userinfo с access token.</li>
</ol>
<a
href={endpoints.openIdConfigurationUrl}
target="_blank"
rel="noreferrer"
className="mt-2 inline-flex items-center gap-1 text-xs text-[#1d4ed8] hover:underline"
>
Открыть OpenID Configuration
<ExternalLink className="h-3.5 w-3.5" />
</a>
</section>
{client.canManage ? (
<div className="flex flex-wrap gap-2 border-t border-[#eceef4] pt-4">
{client.type !== 'PUBLIC' ? (
<Button variant="secondary" size="sm" onClick={() => onRotateSecret(client.clientId)}>
<RefreshCw className="h-4 w-4" />
Новый secret
</Button>
) : null}
<Button variant="secondary" size="sm" onClick={() => onToggleActive(client)}>
{client.isActive ? 'Отключить' : 'Включить'}
</Button>
</div>
) : null}
</div>
</DialogContent>
</Dialog>
);
}