mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
## What kind of change does this PR introduce? Refactor. Follow-up to #49871. ## What is the current behavior? Branded (green) text still uses the `text-brand` classname while the colour comes from `--primary`. ## What is the new behavior? **Rename-only:** `text-brand` → `text-primary` across callsites and docs. Leftover `bg-brand` / `border-brand` alias to `brand-default`. No intentional colour changes in this PR. This better matches how we treat our green in other components and props, like `variant="primary"` for green buttons. ## To test On light mode: smoke-test that branded text still looks like #49871 (readable green, not the bright fill). - [Homepage](https://zone-www-dot-com-git-dnywh-depr-316-text-brand-de2380-supabase.vercel.app/): “Scale to millions” uses `text-primary` - [Docs homepage](https://docs-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/docs): branded links like “More on self-hosting” are still readable - [Typography docs](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/typography): documents `text-primary` - [Colour usage](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/color-usage): `text-primary` is visibly darker than `bg-brand-default` _on light mode_ - [Studio auth providers](https://studio-staging-git-dnywh-depr-316-text-brand-to-60fa6c-supabase.vercel.app/dashboard/project/_/auth/providers): enabled provider badge text readable; status dot stays bright green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Updated branded text, link hover states, icons, badges, indicators, and highlighted content across the Design System, Docs, Studio, Learn, UI Library, and marketing experiences to use the primary theme color. * Updated syntax highlighting and table-of-contents styling for consistent primary-color presentation. * Refined brand color fallback behavior for bright fills and borders. * **Documentation** * Updated color-usage and typography guidance to recommend the primary text utility. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
588 lines
25 KiB
TypeScript
588 lines
25 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import {
|
|
JwtSecretUpdateError,
|
|
JwtSecretUpdateProgress,
|
|
JwtSecretUpdateStatus,
|
|
} from '@supabase/shared-types/out/events'
|
|
import { IS_PLATFORM, useFlag, useParams } from 'common'
|
|
import {
|
|
AlertCircle,
|
|
ChevronDown,
|
|
CloudOff,
|
|
ExternalLink,
|
|
Hourglass,
|
|
Key,
|
|
Lightbulb,
|
|
Loader2,
|
|
PenTool,
|
|
Power,
|
|
RefreshCw,
|
|
TriangleAlert,
|
|
} from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useState, type Dispatch, type SetStateAction } from 'react'
|
|
import { useForm } from 'react-hook-form'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
DialogTitle,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns/Admonition'
|
|
import { Input } from 'ui-patterns/DataInputs/Input'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
import { FormLayout } from 'ui-patterns/form/Layout/FormLayout'
|
|
import * as z from 'zod'
|
|
|
|
import {
|
|
JWT_SECRET_UPDATE_ERROR_MESSAGES,
|
|
JWT_SECRET_UPDATE_PROGRESS_MESSAGES,
|
|
} from './jwt.constants'
|
|
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
|
import { InlineLink } from '@/components/ui/InlineLink'
|
|
import Panel from '@/components/ui/Panel'
|
|
import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper'
|
|
import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query'
|
|
import { useJwtSecretUpdateMutation } from '@/data/config/jwt-secret-update-mutation'
|
|
import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
|
|
import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query'
|
|
import { useLegacyJWTSigningKeyQuery } from '@/data/jwt-signing-keys/legacy-jwt-signing-key-query'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { DOCS_URL } from '@/lib/constants'
|
|
import { uuidv4 } from '@/lib/helpers'
|
|
|
|
const customJwtSecretFormSchema = z.object({
|
|
customToken: z
|
|
.string()
|
|
.min(32, 'Must be at least 32 characters')
|
|
.regex(/^(?!.*[@$]).*$/, '@ and $ are not allowed'),
|
|
})
|
|
const customJwtSecretFormId = 'custom-jwt-secret-form'
|
|
|
|
export const JWTSettings = () => {
|
|
const { ref: projectRef } = useParams()
|
|
|
|
const disableLegacyJwtSecretRotation = useFlag('disableLegacyJwtSecretRotation')
|
|
|
|
const [customToken, setCustomToken] = useState<string>('')
|
|
const [isCreatingKey, setIsCreatingKey] = useState<boolean>(false)
|
|
const [isRegeneratingKey, setIsGeneratingKey] = useState<boolean>(false)
|
|
|
|
const { can: canReadJWTSecret } = useAsyncCheckPermissions(
|
|
PermissionAction.READ,
|
|
'field.jwt_secret'
|
|
)
|
|
const { can: canGenerateNewJWTSecret } = useAsyncCheckPermissions(
|
|
PermissionAction.INFRA_EXECUTE,
|
|
'queue_job.projects.update_jwt'
|
|
)
|
|
|
|
const { data } = useJwtSecretUpdatingStatusQuery({ projectRef }, { enabled: IS_PLATFORM })
|
|
const { data: config, isError } = useProjectPostgrestConfigQuery({ projectRef })
|
|
const { mutateAsync: updateJwt, isPending: isSubmittingJwtSecretUpdateRequest } =
|
|
useJwtSecretUpdateMutation()
|
|
|
|
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
|
|
const { data: legacyKey, isPending } = useLegacyJWTSigningKeyQuery(
|
|
{ projectRef },
|
|
{ enabled: IS_PLATFORM && canReadAPIKeys, retry: false }
|
|
)
|
|
const { data: legacyAPIKeysStatus } = useLegacyAPIKeysStatusQuery(
|
|
{ projectRef },
|
|
{ enabled: IS_PLATFORM && canReadAPIKeys }
|
|
)
|
|
|
|
const { Failed, Updated, Updating } = JwtSecretUpdateStatus
|
|
|
|
const isJwtSecretUpdateFailed = data?.jwtSecretUpdateStatus === Failed
|
|
const isNotUpdatingJwtSecret =
|
|
data?.jwtSecretUpdateStatus === undefined || data?.jwtSecretUpdateStatus === Updated
|
|
const isUpdatingJwtSecret = data?.jwtSecretUpdateStatus === Updating
|
|
const jwtSecretUpdateErrorMessage =
|
|
JWT_SECRET_UPDATE_ERROR_MESSAGES[data?.jwtSecretUpdateError as JwtSecretUpdateError]
|
|
const jwtSecretUpdateProgressMessage =
|
|
JWT_SECRET_UPDATE_PROGRESS_MESSAGES[data?.jwtSecretUpdateProgress as JwtSecretUpdateProgress]
|
|
|
|
const customJwtSecretForm = useForm<z.infer<typeof customJwtSecretFormSchema>>({
|
|
defaultValues: { customToken: '' },
|
|
resolver: zodResolver(customJwtSecretFormSchema),
|
|
})
|
|
|
|
async function handleJwtSecretUpdate(
|
|
jwt_secret: string,
|
|
setModalVisibility: Dispatch<SetStateAction<boolean>>
|
|
) {
|
|
if (!projectRef) return console.error('Project ref is required')
|
|
const trackingId = uuidv4()
|
|
try {
|
|
await updateJwt({ projectRef, jwtSecret: jwt_secret, changeTrackingId: trackingId })
|
|
setModalVisibility(false)
|
|
toast(
|
|
'Successfully submitted JWT secret update request. Please wait while your project is updated.'
|
|
)
|
|
} catch (error: any) {
|
|
toast.error(`Failed to update JWT secret: ${error.message}`)
|
|
}
|
|
}
|
|
|
|
if (!IS_PLATFORM) {
|
|
return (
|
|
<Panel>
|
|
<Panel.Content className="border-t border-panel-border-interior-light in-data-[theme*=dark]:border-panel-border-interior-dark">
|
|
<FormLayout
|
|
layout="flex-row-reverse"
|
|
id="JWT_SECRET"
|
|
label="JWT secret"
|
|
description="Used to verify legacy user session JWTs issued by Supabase Auth."
|
|
>
|
|
<Input id="JWT_SECRET" copy reveal readOnly value={config?.jwt_secret || ''} />
|
|
</FormLayout>
|
|
</Panel.Content>
|
|
</Panel>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Panel>
|
|
<Panel.Content className="border-t border-panel-border-interior-light in-data-[theme*=dark]:border-panel-border-interior-dark">
|
|
<div className="space-y-6">
|
|
{isError ? (
|
|
<div className="flex items-center justify-center py-8 space-x-2">
|
|
<AlertCircle size={16} strokeWidth={1.5} />
|
|
<p className="text-sm text-foreground-light">Failed to retrieve JWT settings</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{legacyKey && legacyKey.status !== 'revoked' && (
|
|
<Admonition
|
|
type="warning"
|
|
title="Legacy JWT secret has been migrated to new JWT Signing Keys"
|
|
>
|
|
<p className="leading-normal!">
|
|
Legacy JWT secret can only be changed by rotating to a standby key and then
|
|
revoking it. It is used to{' '}
|
|
<em className="text-foreground not-italic">
|
|
{legacyKey.status === 'in_use' ? 'sign and verify' : 'only verify'}
|
|
</em>{' '}
|
|
JSON Web Tokens by Supabase products.
|
|
</p>
|
|
|
|
{legacyAPIKeysStatus && legacyAPIKeysStatus.enabled && (
|
|
<p className="leading-normal!">
|
|
<em className="text-warning not-italic">
|
|
This includes the <code className="text-code-inline">anon</code> and{' '}
|
|
<code className="text-code-inline">service_role</code> JWT based API keys.
|
|
</em>{' '}
|
|
Consider switching to publishable and secret API keys to disable them.
|
|
</p>
|
|
)}
|
|
|
|
<Button asChild icon={<ExternalLink />} className="mt-2">
|
|
<Link href={`/project/${projectRef}/settings/api-keys`}>Go to API keys</Link>
|
|
</Button>
|
|
</Admonition>
|
|
)}
|
|
{legacyKey && legacyKey.status === 'revoked' && (
|
|
<Admonition
|
|
type="note"
|
|
title="Your project has revoked the legacy JWT secret"
|
|
description="No new JSON Web Tokens are issued nor verified with it by Supabase products."
|
|
/>
|
|
)}
|
|
<FormLayout
|
|
layout="flex-row-reverse"
|
|
id="JWT_SECRET"
|
|
label={
|
|
legacyKey?.status === 'revoked'
|
|
? 'Revoked legacy JWT secret'
|
|
: legacyKey
|
|
? 'Legacy JWT secret (still used)'
|
|
: 'Legacy JWT secret'
|
|
}
|
|
description={
|
|
legacyKey?.status === 'revoked'
|
|
? 'No longer used to sign JWTs by Supabase Auth.'
|
|
: !legacyKey || legacyKey.status === 'in_use'
|
|
? 'Used to sign and verify JWTs issued by Supabase Auth.'
|
|
: 'Used only to verify JWTs.'
|
|
}
|
|
>
|
|
<Input
|
|
id="JWT_SECRET"
|
|
copy={canReadJWTSecret && isNotUpdatingJwtSecret}
|
|
reveal={canReadJWTSecret && isNotUpdatingJwtSecret}
|
|
readOnly
|
|
value={
|
|
!canReadJWTSecret
|
|
? 'You need additional permissions to view the JWT secret'
|
|
: isJwtSecretUpdateFailed
|
|
? 'JWT secret update failed'
|
|
: isUpdatingJwtSecret
|
|
? 'Updating JWT secret...'
|
|
: config?.jwt_secret || ''
|
|
}
|
|
/>
|
|
</FormLayout>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{!isPending && !legacyKey && (
|
|
<>
|
|
{isUpdatingJwtSecret && (
|
|
<div className="flex items-center space-x-2">
|
|
<Loader2 className="animate-spin" size={14} />
|
|
<p className="text-sm">Updating JWT secret: {jwtSecretUpdateProgressMessage}</p>
|
|
</div>
|
|
)}
|
|
|
|
{isJwtSecretUpdateFailed && (
|
|
<Admonition type="warning" title="Failed to update JWT secret">
|
|
Please try again. If the failures persist, please contact Supabase support with
|
|
the following details: <br />
|
|
Change tracking ID: {data?.changeTrackingId} <br />
|
|
Error message: {jwtSecretUpdateErrorMessage}
|
|
</Admonition>
|
|
)}
|
|
|
|
<Collapsible className="bg border rounded-md mt-4">
|
|
<CollapsibleTrigger className="p-4 w-full flex items-center justify-between [&[data-state=open]>svg]:-rotate-180!">
|
|
<p className="text-sm">
|
|
{disableLegacyJwtSecretRotation
|
|
? 'How to migrate to the new API keys?'
|
|
: 'How to change your JWT secret?'}
|
|
</p>
|
|
<ChevronDown size={14} className="transition-transform duration-200" />
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent className="border-t p-4">
|
|
<p className="text-sm text-foreground-light text-balance mb-2">
|
|
{disableLegacyJwtSecretRotation
|
|
? 'Migrate to the new publishable and secret API keys to enable rotation with zero downtime and without signing users out. The change is reversible until you revoke the legacy secret.'
|
|
: 'Instead of changing the legacy JWT secret use a combination of the JWT Signing Keys and API keys features. Consider these advantages:'}
|
|
</p>
|
|
|
|
{disableLegacyJwtSecretRotation ? (
|
|
<ol className="text-sm text-foreground-light list-decimal list-outside pl-7 space-y-2">
|
|
<li>
|
|
<p className="text-foreground">
|
|
Click "Migrate JWT secret" in{' '}
|
|
<InlineLink href={`/project/${projectRef}/settings/jwt`}>
|
|
JWT Signing Keys
|
|
</InlineLink>
|
|
.
|
|
</p>
|
|
<p className="text-foreground-lighter">
|
|
This imports your legacy secret into the new system and generates a
|
|
standby asymmetric key.
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<p className="text-foreground">Create and roll out new API keys.</p>
|
|
<p className="text-foreground-lighter">
|
|
In{' '}
|
|
<InlineLink href={`/project/${projectRef}/settings/api-keys`}>
|
|
API Keys
|
|
</InlineLink>
|
|
, create a publishable key and secret key, then swap them into your apps
|
|
in place of <code className="text-code-inline">anon</code> and{' '}
|
|
<code className="text-code-inline break-keep!">service_role</code>{' '}
|
|
respectively. Watch the "Last used" indicators to confirm no traffic still
|
|
depends on the legacy keys.
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<p className="text-foreground">
|
|
Click "Rotate keys" in{' '}
|
|
<InlineLink href={`/project/${projectRef}/settings/jwt`}>
|
|
JWT Signing Keys
|
|
</InlineLink>{' '}
|
|
to start signing new JWTs with the standby key.
|
|
</p>
|
|
<p className="text-foreground-lighter">
|
|
Existing <code className="text-code-inline">anon</code>,{' '}
|
|
<code className="text-code-inline">service_role</code>, and active user
|
|
JWTs stay valid. Before rotating, switch any code that verifies JWTs
|
|
directly against the legacy secret (e.g.{' '}
|
|
<code className="text-code-inline">jose</code>,{' '}
|
|
<code className="text-code-inline">jsonwebtoken</code>) to{' '}
|
|
<code className="text-code-inline">supabase.auth.getClaims()</code> or a
|
|
JWKS-based verifier, and disable the "Verify JWT" setting on any affected
|
|
Edge Functions.
|
|
</p>
|
|
</li>
|
|
<li>
|
|
<p className="text-foreground">
|
|
Optionally, revoke the legacy JWT secret in{' '}
|
|
<InlineLink href={`/project/${projectRef}/settings/jwt`}>
|
|
JWT Signing Keys
|
|
</InlineLink>{' '}
|
|
once you're sure it's no longer in use.
|
|
</p>
|
|
</li>
|
|
</ol>
|
|
) : (
|
|
<ul className="text-sm text-foreground-light list-disc list-inside">
|
|
<li>Zero-downtime, reversible change.</li>
|
|
<li>Users remain signed in and bad actors out.</li>
|
|
<li>
|
|
Create multiple secret API keys that are immediately revocable and fully
|
|
covered by audit logs.
|
|
</li>
|
|
<li>
|
|
Private keys and shared secrets are no longer visible by organization
|
|
members, so they can't leak.
|
|
</li>
|
|
<li>
|
|
Maintain tighter alignment with SOC2 and other security compliance
|
|
frameworks.
|
|
</li>
|
|
<li>
|
|
Improve app's performance by using public keys to verify JWTs instead of
|
|
calling <code className="text-code-inline">getUser()</code>.
|
|
</li>
|
|
</ul>
|
|
)}
|
|
|
|
<div className="flex flex-row gap-x-2 mt-4">
|
|
{disableLegacyJwtSecretRotation ? (
|
|
<Button icon={<ExternalLink className="size-4" />} asChild>
|
|
<Link
|
|
href={`${DOCS_URL}/guides/auth/signing-keys#getting-started`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Read the full migration guide
|
|
</Link>
|
|
</Button>
|
|
) : (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<ButtonTooltip
|
|
disabled={!canGenerateNewJWTSecret}
|
|
iconRight={<ChevronDown size={14} />}
|
|
loading={isUpdatingJwtSecret}
|
|
tooltip={{
|
|
content: {
|
|
side: 'bottom',
|
|
text: !canGenerateNewJWTSecret
|
|
? 'You need additional permissions to generate a new JWT secret'
|
|
: undefined,
|
|
},
|
|
}}
|
|
>
|
|
Change legacy JWT secret
|
|
</ButtonTooltip>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" side="bottom">
|
|
<DropdownMenuItem
|
|
className="space-x-2"
|
|
onClick={() => setIsGeneratingKey(true)}
|
|
>
|
|
<RefreshCw size={16} />
|
|
<p>Generate a random secret</p>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
className="space-x-2"
|
|
onClick={() => setIsCreatingKey(true)}
|
|
>
|
|
<PenTool size={16} />
|
|
<p>Create my own secret</p>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</div>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
</>
|
|
)}
|
|
</Panel.Content>
|
|
</Panel>
|
|
|
|
<TextConfirmModal
|
|
variant="destructive"
|
|
size="large"
|
|
visible={isRegeneratingKey && !disableLegacyJwtSecretRotation}
|
|
title="Confirm legacy JWT secret change"
|
|
confirmString="I understand and wish to proceed"
|
|
confirmLabel={customToken ? 'Apply custom secret' : 'Generate random secret'}
|
|
confirmPlaceholder=""
|
|
loading={isSubmittingJwtSecretUpdateRequest}
|
|
onCancel={() => {
|
|
setIsGeneratingKey(false)
|
|
setCustomToken('')
|
|
}}
|
|
onConfirm={() => handleJwtSecretUpdate(customToken || 'ROLL', setIsGeneratingKey)}
|
|
>
|
|
<ul className="space-y-4 text-sm">
|
|
<li className="flex gap-2 bg border rounded-md p-4">
|
|
<Lightbulb size={24} className="shrink-0 text-primary" />
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<p>Use new JWT Signing Keys and API Keys instead</p>
|
|
<p className="text-foreground-light">
|
|
Consider using a combination of the JWT Signing Keys and API Keys features to
|
|
achieve the same effect.{' '}
|
|
<em className="text-primary not-italic">
|
|
Some or all of the warnings listed below might not apply when using these features
|
|
</em>
|
|
.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
<li className="flex gap-2 px-4">
|
|
<CloudOff size={24} className="text-foreground-light shrink-0" />
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<p>Your application will experience significant downtime</p>
|
|
<p className="text-foreground-light">
|
|
As new <code>anon</code> and <code>service_role</code> keys will be created and the
|
|
existing ones permanently destroyed, your application will stop functioning for the
|
|
duration it takes you to swap them.{' '}
|
|
<em className="text-warning not-italic">
|
|
If you have a mobile, desktop, CLI or any offline-capable application the downtime
|
|
may be more significant and dependent on app store reviews or user-initiated
|
|
upgrades or downloads!
|
|
</em>
|
|
</p>
|
|
<p className="text-foreground-light">
|
|
Currently active users will be forcefully signed out (inactive users will keep their
|
|
sessions).
|
|
</p>
|
|
<p className="text-foreground-light">
|
|
All long-lived Storage pre-signed URLs will be permanently invalidated.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
|
|
<li className="flex gap-2 px-4">
|
|
<Power size={24} className="text-foreground-light shrink-0" />
|
|
<div className="flex flex-col gap-2">
|
|
<p>Your project and database will be restarted</p>
|
|
<p className="text-foreground-light">
|
|
This process restarts your project, terminating existing connections to your
|
|
database. You may see API or other unusual errors for{' '}
|
|
<em className="text-warning not-italic">up to 2 minutes</em> while the new secret is
|
|
deployed.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
<li className="flex gap-2 px-4">
|
|
<Hourglass size={24} className="text-foreground-light shrink-0" />
|
|
<div className="flex flex-col gap-2">
|
|
<p>20-minute cooldown period</p>
|
|
<p className="text-foreground-light">
|
|
Should you need to revert or repeat this operation, it will take at least 20 minutes
|
|
before you're able to do so again.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
<li className="flex gap-2 px-4">
|
|
<TriangleAlert size={24} className="text-foreground-light shrink-0" />
|
|
<div className="flex flex-col gap-2">
|
|
<p>Irreversible change! This cannot be undone!</p>
|
|
<p className="text-foreground-light">
|
|
The old JWT secret will be permanently lost (unless you've saved it prior). Even if
|
|
you use it again the <code>anon</code> and <code>service_role</code> API keys{' '}
|
|
<em className="text-warning not-italic">will not be restorable</em> to their exact
|
|
values.
|
|
</p>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</TextConfirmModal>
|
|
|
|
<Dialog
|
|
open={isCreatingKey && !disableLegacyJwtSecretRotation}
|
|
onOpenChange={() => {
|
|
setIsCreatingKey(false)
|
|
setCustomToken('')
|
|
customJwtSecretForm.reset({ customToken: '' })
|
|
}}
|
|
>
|
|
<DialogContent size="medium">
|
|
<DialogHeader>
|
|
<DialogTitle>Pick a new JWT secret</DialogTitle>
|
|
<DialogDescription>
|
|
Pick a new custom JWT secret. Make sure it is a strong combination of characters that
|
|
cannot be guessed easily.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogSectionSeparator />
|
|
<DialogSection>
|
|
<Form {...customJwtSecretForm}>
|
|
<form
|
|
id={customJwtSecretFormId}
|
|
onSubmit={customJwtSecretForm.handleSubmit((values) => {
|
|
setIsGeneratingKey(true)
|
|
setIsCreatingKey(false)
|
|
setCustomToken(values.customToken)
|
|
})}
|
|
className="flex flex-col space-y-2"
|
|
noValidate
|
|
>
|
|
<FormField
|
|
control={customJwtSecretForm.control}
|
|
name="customToken"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="vertical"
|
|
label="Custom JWT secret"
|
|
description="Minimally 32 characters long, '@' and '$' are not allowed."
|
|
>
|
|
<FormControl>
|
|
<Input copy reveal icon={<Key />} className="w-full text-left" {...field} />
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</form>
|
|
</Form>
|
|
</DialogSection>
|
|
<DialogFooter>
|
|
<Button
|
|
onClick={() => {
|
|
setIsCreatingKey(false)
|
|
setCustomToken('')
|
|
customJwtSecretForm.reset({ customToken: '' })
|
|
}}
|
|
disabled={isSubmittingJwtSecretUpdateRequest}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
type="submit"
|
|
form={customJwtSecretFormId}
|
|
loading={isSubmittingJwtSecretUpdateRequest}
|
|
disabled={isSubmittingJwtSecretUpdateRequest}
|
|
>
|
|
Proceed to final confirmation
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|