fix and update
This commit is contained in:
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
137
apps/frontend/components/id/oauth-client-one-tap-section.tsx
Normal file
137
apps/frontend/components/id/oauth-client-one-tap-section.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user