From 091468b77647d14d17984d9d71e1602cb8666edc Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 23 Jan 2025 21:41:35 -0600 Subject: [PATCH] Generate client-side secrets for admin auth --- src/auth/actions.ts | 3 ++ src/components/CopyButton.tsx | 32 ++++++++++++++++++++ src/site/SecretGenerator.tsx | 51 ++++++++++++++++++++++++++++++++ src/site/SiteChecklistClient.tsx | 43 ++++----------------------- src/site/SiteChecklistServer.tsx | 3 -- 5 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/components/CopyButton.tsx create mode 100644 src/site/SecretGenerator.tsx diff --git a/src/auth/actions.ts b/src/auth/actions.ts index 9f26b610..72c04dea 100644 --- a/src/auth/actions.ts +++ b/src/auth/actions.ts @@ -6,6 +6,7 @@ import { KEY_CREDENTIALS_SIGN_IN_ERROR, KEY_CREDENTIALS_SIGN_IN_ERROR_URL, auth, + generateAuthSecret, signIn, signOut, } from '@/auth'; @@ -47,3 +48,5 @@ export const getAuthAction = async () => auth(); export const logClientAuthUpdate = async (data: Session | null | undefined) => console.log('Client auth update', data); + +export const generateAuthSecretAction = async () => generateAuthSecret(); diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx new file mode 100644 index 00000000..7c4fa939 --- /dev/null +++ b/src/components/CopyButton.tsx @@ -0,0 +1,32 @@ +import { BiCopy } from 'react-icons/bi'; +import LoaderButton from './primitives/LoaderButton'; +import clsx from 'clsx/lite'; +import { toastSuccess } from '@/toast'; + +export default function CopyButton({ + label, + text, + subtle, +}: { + label: string + text?: string, + subtle?: boolean +}) { + return ( + } + className={clsx( + 'translate-y-[2px]', + subtle && 'text-gray-300 dark:text-gray-700', + )} + onClick={text + ? () => { + navigator.clipboard.writeText(text); + toastSuccess(`${label} copied to clipboard`); + } + : undefined} + styleAs="link" + disabled={!text} + /> + ); +} diff --git a/src/site/SecretGenerator.tsx b/src/site/SecretGenerator.tsx new file mode 100644 index 00000000..c85452d6 --- /dev/null +++ b/src/site/SecretGenerator.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { clsx } from 'clsx/lite'; +import Container from '@/components/Container'; +import Spinner from '@/components/Spinner'; +import CopyButton from '@/components/CopyButton'; +import { useCallback, useEffect, useState } from 'react'; +import { generateAuthSecretAction } from '@/auth/actions'; +import { BiRefresh } from 'react-icons/bi'; + +export default function SecretGenerator() { + const [isLoading, setIsLoading] = useState(false); + const [secret, setSecret] = useState(''); + + const getSecret = useCallback(async () => { + setIsLoading(true); + await generateAuthSecretAction() + .then(setSecret) + .finally(() => setIsLoading(false)); + }, []); + + useEffect(() => { + getSecret(); + }, [getSecret]); + + return ( +
+ +
+ {secret ? {secret} : } +
+ +
+
+
+ {secret &&
+ {isLoading + ? + : } +
} +
+ ); +} diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 803d6add..3b2ee2c9 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -9,26 +9,23 @@ import ChecklistRow from '../components/ChecklistRow'; import { FiExternalLink } from 'react-icons/fi'; import { BiCog, - BiCopy, BiData, BiHide, BiLockAlt, BiPencil, } from 'react-icons/bi'; -import Container from '@/components/Container'; import Checklist from '@/components/Checklist'; -import { toastSuccess } from '@/toast'; import { ConfigChecklistStatus } from './config'; import StatusIcon from '@/components/StatusIcon'; import { labelForStorage } from '@/services/storage'; import { HiSparkles } from 'react-icons/hi'; -import LoaderButton from '@/components/primitives/LoaderButton'; import { testConnectionsAction } from '@/admin/actions'; import ErrorNote from '@/components/ErrorNote'; -import Spinner from '@/components/Spinner'; import WarningNote from '@/components/WarningNote'; import { RiSpeedMiniLine } from 'react-icons/ri'; import Link from 'next/link'; +import SecretGenerator from './SecretGenerator'; +import CopyButton from '@/components/CopyButton'; export default function SiteChecklistClient({ // Storage @@ -94,12 +91,10 @@ export default function SiteChecklistClient({ // Component props simplifiedView, isTestingConnections, - secret, }: ConfigChecklistStatus & Partial>> & { simplifiedView?: boolean isTestingConnections?: boolean - secret?: string }) { const renderLink = (href: string, text: string, external = true) => <> @@ -122,23 +117,6 @@ export default function SiteChecklistClient({ } ; - const renderCopyButton = (label: string, text?: string, subtle?: boolean) => - } - className={clsx( - 'translate-y-[2px]', - subtle && 'text-gray-300 dark:text-gray-700', - )} - onClick={text - ? () => { - navigator.clipboard.writeText(text); - toastSuccess(`${label} copied to clipboard`); - } - : undefined} - styleAs="link" - disabled={!text} - />; - const renderEnvVar = ( variable: string, minimal?: boolean, @@ -159,7 +137,7 @@ export default function SiteChecklistClient({ )}> `{variable}` - {!minimal && renderCopyButton(variable, variable, true)} + {!minimal && } ; @@ -321,20 +299,9 @@ export default function SiteChecklistClient({ isPending={!hasAuthSecret && isTestingConnections} > Store auth secret in environment variable: - {!hasAuthSecret || true && + {!hasAuthSecret &&
- -
- {secret ? {secret} : } -
- {renderCopyButton('Secret', secret)} -
-
-
+
} {renderEnvVars(['AUTH_SECRET'])} diff --git a/src/site/SiteChecklistServer.tsx b/src/site/SiteChecklistServer.tsx index 1d264d19..af73d43b 100644 --- a/src/site/SiteChecklistServer.tsx +++ b/src/site/SiteChecklistServer.tsx @@ -1,4 +1,3 @@ -import { generateAuthSecret } from '@/auth'; import SiteChecklistClient from './SiteChecklistClient'; import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; import { testConnectionsAction } from '@/admin/actions'; @@ -8,14 +7,12 @@ export default async function SiteChecklistServer({ }: { simplifiedView?: boolean }) { - const secret = await generateAuthSecret().catch(() => 'TRY AGAIN'); const connectionErrors = await testConnectionsAction().catch(() => ({})); return ( ); }