diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index dadfcdd..4d4c929 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react' -import { CheckCircle, XCircle, RefreshCw, ChevronDown, ChevronUp, Clock, Folder, FolderOpen, ChevronRight } from 'lucide-react' +import { useState, useEffect, useRef } from 'react' +import { CheckCircle, XCircle, RefreshCw, ChevronDown, ChevronUp, Clock, Folder, FolderOpen, ChevronRight, KeyRound, Copy, Check } from 'lucide-react' import { Sidebar } from '../components/Sidebar' import type { User } from '../types' @@ -123,6 +123,15 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o const [ncBrowseLoading, setNcBrowseLoading] = useState(false) const [ncBrowseError, setNcBrowseError] = useState(null) + // Hosted Tables — auth secret reveal + const [secretPanelOpen, setSecretPanelOpen] = useState(false) + const [secretPassword, setSecretPassword] = useState('') + const [revealedSecret, setRevealedSecret] = useState(null) + const [revealing, setRevealing] = useState(false) + const [revealError, setRevealError] = useState(null) + const [copied, setCopied] = useState(false) + const secretClearTimer = useRef | null>(null) + function openForm() { const initial: Record = {} for (const [k, v] of Object.entries(integration.config)) initial[k] = v ?? '' @@ -133,6 +142,51 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o setOpen(true) } + function openSecretPanel() { + setSecretPassword('') + setRevealedSecret(null) + setRevealError(null) + setCopied(false) + if (secretClearTimer.current) clearTimeout(secretClearTimer.current) + setSecretPanelOpen(true) + } + + function closeSecretPanel() { + setSecretPanelOpen(false) + setRevealedSecret(null) + setSecretPassword('') + if (secretClearTimer.current) clearTimeout(secretClearTimer.current) + } + + async function revealSecret() { + if (!secretPassword) return + setRevealing(true) + setRevealError(null) + const res = await fetch('/api/auth/admin/reveal-secret', { + method: 'POST', credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ password: secretPassword }), + }) + const data = await res.json() + setRevealing(false) + if (!res.ok) { setRevealError(data.error || 'Failed'); return } + setRevealedSecret(data.secret) + setSecretPassword('') + // Auto-clear after 60 seconds + if (secretClearTimer.current) clearTimeout(secretClearTimer.current) + secretClearTimer.current = setTimeout(() => { + setRevealedSecret(null) + setCopied(false) + }, 60_000) + } + + function copySecret() { + if (!revealedSecret) return + navigator.clipboard.writeText(revealedSecret) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + async function ncBrowse(path: string) { setNcBrowseLoading(true) setNcBrowseError(null) @@ -206,15 +260,96 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o - +
+ {integration.slug === 'hosted_tables' && ( + + )} + +
+ {secretPanelOpen && ( +
+

+ Re-enter your admin password to reveal the CENTRAL_AUTH_SECRET. + Set this as the CENTRAL_AUTH_SECRET environment variable in Hosted Tables to enable shared session SSO. +

+ + {!revealedSecret ? ( +
+ + {revealError && ( +
+ {revealError} +
+ )} +
+ +
+
+ ) : ( +
+
+ + {revealedSecret} + + +
+

+ Secret will be hidden automatically after 60 seconds. +

+
+ )} +
+ )} + {open && (