diff --git a/apps/docs/content/guides/auth/signing-keys.mdx b/apps/docs/content/guides/auth/signing-keys.mdx index f78a3f671ba..672979e4c08 100644 --- a/apps/docs/content/guides/auth/signing-keys.mdx +++ b/apps/docs/content/guides/auth/signing-keys.mdx @@ -36,7 +36,7 @@ We've designed the Signing keys system to address many problems the legacy syste You can start migrating away from the legacy JWT secret through the Supabase dashboard. This process does not cause downtime for your application. -1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt/signing-keys) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system. +1. Start off by clicking the _Migrate JWT secret_ button on the [JWT signing keys](/dashboard/project/_/settings/jwt) page. This step will import the existing legacy JWT secret into the new JWT signing keys system. Once this process completes, you will no longer be able to rotate the legacy JWT secret using the old system. 2. Simultaneously, we're creating a new asymmetric JWT signing key for you to rotate to. This key starts off as standby key -- meaning it's being advertised as a key that Supabase Auth will use in the future to create JWTs. 3. If you're not ready to switch away from the legacy JWT secret right now, you can stop here without any issue. If you wish to use a different signing key -- either to use a different signing algorithm (RSA, Elliptic Curve or shared secret) or to import a private key or shared secret you already have -- feel free to move the standby key to _Previously used_ before finally moving it to _Revoked._ 4. If you do wish to start using the standby key for all new JWT use the _Rotate keys_ button. A few important notes: @@ -176,7 +176,7 @@ supabase gen signing-key --algorithm ES256 Make sure you store this private key in a secure location, as it will not be extractable from Supabase. -To import the generated private key to your project, create a [new standby key](/dashboard/project/_/settings/jwt/signing-keys) from the dashboard: +To import the generated private key to your project, create a [new standby key](/dashboard/project/_/settings/jwt) from the dashboard: ```json { diff --git a/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx b/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx index 03ea980a382..7b004ca2b33 100644 --- a/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx +++ b/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx @@ -3,27 +3,21 @@ import { ScaffoldContainer } from 'components/layouts/Scaffold' import { PropsWithChildren } from 'react' import { useParams } from 'common' -import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' const JWTKeysLayout = ({ children }: PropsWithChildren) => { const { ref: projectRef } = useParams() - const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys']) const navigationItems = [ - ...(projectSettingsLegacyJwtKeys - ? [ - { - label: 'Legacy JWT Secret', - href: `/project/${projectRef}/settings/jwt`, - id: 'legacy-jwt-keys', - }, - ] - : []), { label: 'JWT Signing Keys', - href: `/project/${projectRef}/settings/jwt/signing-keys`, + href: `/project/${projectRef}/settings/jwt`, id: 'signing-keys', }, + { + label: 'Legacy JWT Secret', + href: `/project/${projectRef}/settings/jwt/legacy`, + id: 'legacy-jwt-keys', + }, ] return ( diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js index 78783e6833b..4770c4ea9bb 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.js @@ -242,6 +242,11 @@ const nextConfig = { destination: '/org/_/billing', permanent: true, }, + { + permanent: true, + source: '/project/:ref/settings/jwt/signing-keys', + destination: '/project/:ref/settings/jwt', + }, { source: '/project/:ref/database/api-logs', destination: '/project/:ref/logs/edge-logs', diff --git a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx index ea17e0136c0..1013828e9ed 100644 --- a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx +++ b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx @@ -1,70 +1,39 @@ -import JWTSettings from 'components/interfaces/JwtSecrets/jwt-settings' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { JWTSecretKeysTable } from 'components/interfaces/JwtSecrets/jwt-secret-keys-table' import DefaultLayout from 'components/layouts/DefaultLayout' import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout' import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' +import NoPermission from 'components/ui/NoPermission' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' -import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' -import { useQueryClient } from '@tanstack/react-query' -import { useParams } from 'common' -import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from 'components/interfaces/JwtSecrets/jwt.constants' -import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' -import { configKeys } from 'data/config/keys' -import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' -import { useEffect, useRef } from 'react' -import { toast } from 'sonner' - -const JWTKeysLegacyPage: NextPageWithLayout = () => { - const client = useQueryClient() - const { ref: projectRef } = useParams() - const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys']) - - const { data } = useJwtSecretUpdatingStatusQuery( - { projectRef }, - { enabled: projectSettingsLegacyJwtKeys } +const JWTSigningKeysPage: NextPageWithLayout = () => { + const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'auth_signing_keys' ) - const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus - const jwtSecretUpdateError = data?.jwtSecretUpdateError - - const previousJwtSecretUpdateStatus = useRef() - const { Failed, Updated, Updating } = JwtSecretUpdateStatus - const jwtSecretUpdateErrorMessage = - JWT_SECRET_UPDATE_ERROR_MESSAGES[jwtSecretUpdateError as JwtSecretUpdateError] - - useEffect(() => { - if (previousJwtSecretUpdateStatus.current === Updating) { - switch (jwtSecretUpdateStatus) { - case Updated: - client.invalidateQueries({ queryKey: configKeys.api(projectRef) }) - client.invalidateQueries({ queryKey: configKeys.settings(projectRef) }) - client.invalidateQueries({ queryKey: configKeys.postgrest(projectRef) }) - toast.success('Successfully updated JWT secret') - break - case Failed: - toast.error(`JWT secret update failed: ${jwtSecretUpdateErrorMessage}`) - break - } - } - - previousJwtSecretUpdateStatus.current = jwtSecretUpdateStatus - }, [jwtSecretUpdateStatus]) - - if (!projectSettingsLegacyJwtKeys) { - return - } return ( - - - + <> + {!isPermissionsLoaded ? ( + + ) : !canReadAPIKeys ? ( + + ) : ( + + )} + ) } -JWTKeysLegacyPage.getLayout = (page) => ( +JWTSigningKeysPage.getLayout = (page) => ( - {page} + + {page} + ) -export default JWTKeysLegacyPage +export default JWTSigningKeysPage diff --git a/apps/studio/pages/project/[ref]/settings/jwt/legacy.tsx b/apps/studio/pages/project/[ref]/settings/jwt/legacy.tsx new file mode 100644 index 00000000000..ea17e0136c0 --- /dev/null +++ b/apps/studio/pages/project/[ref]/settings/jwt/legacy.tsx @@ -0,0 +1,70 @@ +import JWTSettings from 'components/interfaces/JwtSecrets/jwt-settings' +import DefaultLayout from 'components/layouts/DefaultLayout' +import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout' +import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' +import type { NextPageWithLayout } from 'types' + +import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' +import { useQueryClient } from '@tanstack/react-query' +import { useParams } from 'common' +import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from 'components/interfaces/JwtSecrets/jwt.constants' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' +import { configKeys } from 'data/config/keys' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { useEffect, useRef } from 'react' +import { toast } from 'sonner' + +const JWTKeysLegacyPage: NextPageWithLayout = () => { + const client = useQueryClient() + const { ref: projectRef } = useParams() + const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys']) + + const { data } = useJwtSecretUpdatingStatusQuery( + { projectRef }, + { enabled: projectSettingsLegacyJwtKeys } + ) + const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus + const jwtSecretUpdateError = data?.jwtSecretUpdateError + + const previousJwtSecretUpdateStatus = useRef() + const { Failed, Updated, Updating } = JwtSecretUpdateStatus + const jwtSecretUpdateErrorMessage = + JWT_SECRET_UPDATE_ERROR_MESSAGES[jwtSecretUpdateError as JwtSecretUpdateError] + + useEffect(() => { + if (previousJwtSecretUpdateStatus.current === Updating) { + switch (jwtSecretUpdateStatus) { + case Updated: + client.invalidateQueries({ queryKey: configKeys.api(projectRef) }) + client.invalidateQueries({ queryKey: configKeys.settings(projectRef) }) + client.invalidateQueries({ queryKey: configKeys.postgrest(projectRef) }) + toast.success('Successfully updated JWT secret') + break + case Failed: + toast.error(`JWT secret update failed: ${jwtSecretUpdateErrorMessage}`) + break + } + } + + previousJwtSecretUpdateStatus.current = jwtSecretUpdateStatus + }, [jwtSecretUpdateStatus]) + + if (!projectSettingsLegacyJwtKeys) { + return + } + + return ( + + + + ) +} + +JWTKeysLegacyPage.getLayout = (page) => ( + + {page} + +) + +export default JWTKeysLegacyPage diff --git a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx b/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx deleted file mode 100644 index 1013828e9ed..00000000000 --- a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' - -import { JWTSecretKeysTable } from 'components/interfaces/JwtSecrets/jwt-secret-keys-table' -import DefaultLayout from 'components/layouts/DefaultLayout' -import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout' -import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' -import NoPermission from 'components/ui/NoPermission' -import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' -import type { NextPageWithLayout } from 'types' - -const JWTSigningKeysPage: NextPageWithLayout = () => { - const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( - PermissionAction.READ, - 'auth_signing_keys' - ) - - return ( - <> - {!isPermissionsLoaded ? ( - - ) : !canReadAPIKeys ? ( - - ) : ( - - )} - - ) -} - -JWTSigningKeysPage.getLayout = (page) => ( - - - {page} - - -) - -export default JWTSigningKeysPage