fix and update

This commit is contained in:
lendry
2026-06-29 14:40:35 +03:00
parent 75ccbe5fc4
commit 0df7240dc8
21 changed files with 934 additions and 113 deletions

View File

@@ -22,7 +22,8 @@ import {
PublicUser,
refreshAuthSession,
resetPinRequiredNotification,
setPinRequiredHandler
setPinRequiredHandler,
syncFedcmSession
} from '@/lib/api';
import { useToast } from './toast-provider';
import { PinLockModal } from './pin-lock-modal';
@@ -89,6 +90,10 @@ function applySessionState(
setters.activatePinLock(session.sessionId ?? '');
} else {
setters.clearPinLock();
const accessToken = window.localStorage.getItem(AUTH_TOKEN_KEY);
if (accessToken) {
void syncFedcmSession(accessToken);
}
}
}
@@ -154,6 +159,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cacheUserProfile(auth.user);
setHasStoredSession(true);
clearPinLock();
if (auth.pinVerified !== false) {
void syncFedcmSession(auth.accessToken);
}
},
[clearPinLock]
);

View File

@@ -6,6 +6,7 @@ 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';
import { OAuthClientOneTapSection } from '@/components/id/oauth-client-one-tap-section';
function CopyRow({ label, value, onCopy }: { label: string; value: string; onCopy: (value: string, label: string) => void }) {
return (
@@ -24,6 +25,8 @@ function CopyRow({ label, value, onCopy }: { label: string; value: string; onCop
export function OAuthClientDetailDialog({
client,
endpoints,
frontendBase,
projectName,
open,
onOpenChange,
onCopy,
@@ -34,6 +37,8 @@ export function OAuthClientDetailDialog({
}: {
client: OAuthClient | null;
endpoints: OAuthEndpoints;
frontendBase: string;
projectName: string;
open: boolean;
onOpenChange: (open: boolean) => void;
onCopy: (value: string, label: string) => void;
@@ -136,7 +141,7 @@ export function OAuthClientDetailDialog({
<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.
Issuer и endpoints берутся из настроек <strong>PUBLIC_API_URL</strong> и <strong>PUBLIC_FRONTEND_URL</strong> в админке.
</p>
<CopyRow label="Issuer (PUBLIC_API_URL)" value={endpoints.issuer} onCopy={onCopy} />
<CopyRow label="Authorization endpoint" value={endpoints.authorizationEndpoint} onCopy={onCopy} />
@@ -146,6 +151,14 @@ export function OAuthClientDetailDialog({
<CopyRow label="Пример authorize URL" value={sampleAuthorizeUrl} onCopy={onCopy} />
</section>
<OAuthClientOneTapSection
client={client}
endpoints={endpoints}
frontendBase={frontendBase}
projectName={projectName}
onCopy={onCopy}
/>
<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" />

View File

@@ -0,0 +1,137 @@
'use client';
import { useMemo, useState } from 'react';
import { Copy, ExternalLink } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { OAuthClient } from '@/lib/api';
import { buildOneTapExamples } from '@/lib/one-tap-examples';
import { OAuthEndpoints } from '@/lib/oauth-url';
function SnippetBlock({ code, onCopy }: { code: string; onCopy: (value: string, label: string) => void }) {
return (
<div className="relative rounded-2xl bg-[#111827] p-4 text-xs text-[#e5e7eb]">
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-2 top-2 h-8 w-8 text-white hover:bg-white/10 hover:text-white"
aria-label="Скопировать код"
onClick={() => onCopy(code, 'Код интеграции')}
>
<Copy className="h-4 w-4" />
</Button>
<pre className="max-h-72 overflow-auto whitespace-pre-wrap break-all pr-10">{code}</pre>
</div>
);
}
export function OAuthClientOneTapSection({
client,
endpoints,
frontendBase,
projectName,
onCopy
}: {
client: OAuthClient;
endpoints: OAuthEndpoints;
frontendBase: string;
projectName: string;
onCopy: (value: string, label: string) => void;
}) {
const primaryRedirect = client.redirectUris[0] ?? `${frontendBase}/auth/callback`;
const examples = useMemo(
() =>
buildOneTapExamples(
{
apiBase: endpoints.issuer,
frontendBase,
widgetUrl: endpoints.widgetUrl,
fedcmConfigUrl: endpoints.fedcmConfigUrl,
webIdentityUrl: endpoints.webIdentityUrl,
fedcmDiscoverUrl: endpoints.fedcmDiscoverUrl,
projectName
},
client.clientId,
primaryRedirect
),
[client.clientId, endpoints, frontendBase, primaryRedirect, projectName]
);
const [activeTab, setActiveTab] = useState(examples[0]?.id ?? 'widget-script');
return (
<section className="space-y-4 rounded-2xl border border-[#eceef4] p-4">
<div>
<h3 className="font-medium">One Tap Login</h3>
<p className="mt-1 text-xs leading-relaxed text-[#667085]">
FedCM (Chrome/Edge) и виджет <code className="rounded bg-[#f4f5f8] px-1 py-0.5">sso-widget.js</code> с popup fallback.
Убедитесь, что в настройках указаны <strong>PUBLIC_API_URL</strong> и <strong>PUBLIC_FRONTEND_URL</strong>.
</p>
</div>
<div className="grid gap-2 sm:grid-cols-2">
<CopyRowCompact label="FedCM config" value={endpoints.fedcmConfigUrl} onCopy={onCopy} />
<CopyRowCompact label="Web identity" value={endpoints.webIdentityUrl} onCopy={onCopy} />
<CopyRowCompact label="Виджет" value={endpoints.widgetUrl} onCopy={onCopy} />
<CopyRowCompact label="Discover" value={endpoints.fedcmDiscoverUrl} onCopy={onCopy} />
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="mb-2 flex h-auto max-w-full flex-wrap gap-1">
{examples.map((example) => (
<TabsTrigger key={example.id} value={example.id} className="text-xs">
{example.label}
</TabsTrigger>
))}
</TabsList>
{examples.map((example) => (
<TabsContent key={example.id} value={example.id}>
<SnippetBlock code={example.code} onCopy={onCopy} />
</TabsContent>
))}
</Tabs>
<div className="rounded-xl bg-[#eef4ff] px-4 py-3 text-xs text-[#1d4ed8]">
<p className="font-medium">Проверка FedCM</p>
<ol className="mt-2 list-decimal space-y-1 pl-4">
<li>Войдите на IdP ({frontendBase}) в этом браузере.</li>
<li>Откройте сайт клиента с виджетом Chrome покажет нативный диалог или плашку One Tap.</li>
<li>
При ошибке NetworkError проверьте, что <code>PUBLIC_API_URL</code> = <code>{endpoints.issuer}</code>.
</li>
</ol>
<a
href={`${frontendBase.replace(/\/$/, '')}/sso-widget.js`}
target="_blank"
rel="noreferrer"
className="mt-2 inline-flex items-center gap-1 font-medium hover:underline"
>
Открыть sso-widget.js
<ExternalLink className="h-3.5 w-3.5" />
</a>
</div>
</section>
);
}
function CopyRowCompact({
label,
value,
onCopy
}: {
label: string;
value: string;
onCopy: (value: string, label: string) => void;
}) {
return (
<div className="rounded-xl bg-[#f4f5f8] p-3">
<div className="mb-1 flex items-center justify-between gap-2">
<span className="text-xs font-medium">{label}</span>
<Button variant="ghost" size="icon" className="h-7 w-7" aria-label={`Скопировать ${label}`} onClick={() => onCopy(value, label)}>
<Copy className="h-3.5 w-3.5" />
</Button>
</div>
<code className="block break-all text-[11px] text-[#667085]">{value}</code>
</div>
);
}