diff --git a/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.test.ts b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.test.ts new file mode 100644 index 0000000000..edf74a3609 --- /dev/null +++ b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.test.ts @@ -0,0 +1,143 @@ +import { describe, expect, it } from 'vitest' +import { generateAuthMenu, GenerateAuthMenuOptions } from './AuthLayout.utils' + +const allFeaturesEnabled: GenerateAuthMenuOptions = { + ref: 'test-ref', + isPlatform: true, + showOverview: true, + features: { + signInProviders: true, + rateLimits: true, + emails: true, + multiFactor: true, + attackProtection: true, + performance: true, + }, +} + +const allFeaturesDisabled: GenerateAuthMenuOptions = { + ref: 'test-ref', + isPlatform: true, + showOverview: false, + features: { + signInProviders: false, + rateLimits: false, + emails: false, + multiFactor: false, + attackProtection: false, + performance: false, + }, +} + +function flatItemNames(menu: ReturnType): string[] { + return menu.flatMap((group) => group.items.map((item) => item.name)) +} + +function findItem(menu: ReturnType, name: string) { + for (const group of menu) { + const item = group.items.find((i) => i.name === name) + if (item) return item + } + return undefined +} + +describe('generateAuthMenu', () => { + it('platform with all features enabled includes all menu items', () => { + const menu = generateAuthMenu(allFeaturesEnabled) + const names = flatItemNames(menu) + + expect(names).toContain('Overview') + expect(names).toContain('Users') + expect(names).toContain('OAuth Apps') + expect(names).toContain('Email') + expect(names).toContain('Sign In / Providers') + expect(names).toContain('OAuth Server') + expect(names).toContain('Sessions') + expect(names).toContain('Rate Limits') + expect(names).toContain('Multi-Factor') + expect(names).toContain('URL Configuration') + expect(names).toContain('Attack Protection') + expect(names).toContain('Auth Hooks') + expect(names).toContain('Audit Logs') + expect(names).toContain('Performance') + }) + + it('platform with all features disabled shows only core items', () => { + const menu = generateAuthMenu(allFeaturesDisabled) + const names = flatItemNames(menu) + + expect(names).toContain('Users') + expect(names).toContain('OAuth Apps') + expect(names).toContain('Policies') + expect(names).toContain('OAuth Server') + expect(names).toContain('Sessions') + expect(names).toContain('URL Configuration') + expect(names).toContain('Auth Hooks') + expect(names).toContain('Audit Logs') + + expect(names).not.toContain('Overview') + expect(names).not.toContain('Email') + expect(names).not.toContain('Sign In / Providers') + expect(names).not.toContain('Rate Limits') + expect(names).not.toContain('Multi-Factor') + expect(names).not.toContain('Attack Protection') + expect(names).not.toContain('Performance') + }) + + it('self-hosted hides OAuth Apps, Notifications, and platform-only Configuration items', () => { + const menu = generateAuthMenu({ + ...allFeaturesEnabled, + isPlatform: false, + }) + const names = flatItemNames(menu) + const groupTitles = menu.map((g) => g.title) + + expect(names).not.toContain('OAuth Apps') + expect(groupTitles).not.toContain('Notifications') + + // Configuration should only have Policies + const configGroup = menu.find((g) => g.title === 'Configuration')! + expect(configGroup.items).toHaveLength(1) + expect(configGroup.items[0].name).toBe('Policies') + }) + + it('shows Overview when showOverview is true', () => { + const menu = generateAuthMenu({ ...allFeaturesDisabled, showOverview: true }) + expect(flatItemNames(menu)).toContain('Overview') + }) + + it('hides Overview when showOverview is false', () => { + const menu = generateAuthMenu({ ...allFeaturesEnabled, showOverview: false }) + expect(flatItemNames(menu)).not.toContain('Overview') + }) + + it.each([ + ['signInProviders', 'Sign In / Providers'], + ['rateLimits', 'Rate Limits'], + ['emails', 'Email'], + ['multiFactor', 'Multi-Factor'], + ['attackProtection', 'Attack Protection'], + ['performance', 'Performance'], + ] as const)('feature flag %s toggles %s', (flag, itemName) => { + const withEnabled = generateAuthMenu({ + ...allFeaturesDisabled, + features: { ...allFeaturesDisabled.features, [flag]: true }, + }) + const withDisabled = generateAuthMenu({ + ...allFeaturesEnabled, + features: { ...allFeaturesEnabled.features, [flag]: false }, + }) + + expect(flatItemNames(withEnabled)).toContain(itemName) + expect(flatItemNames(withDisabled)).not.toContain(itemName) + }) + + it('generates correct URLs using the ref parameter', () => { + const menu = generateAuthMenu({ ...allFeaturesEnabled, ref: 'my-project' }) + const users = findItem(menu, 'Users') + const oauthApps = findItem(menu, 'OAuth Apps') + + expect(users?.url).toBe('/project/my-project/auth/users') + expect(oauthApps?.url).toBe('/project/my-project/auth/oauth-apps') + }) +}) diff --git a/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts index 1fe0504cf6..972b74567c 100644 --- a/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts +++ b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts @@ -4,50 +4,50 @@ import { IS_PLATFORM } from 'lib/constants' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' -export const useGenerateAuthMenu = (): ProductMenuGroup[] => { - const { ref } = useParams() - const authenticationShowOverview = useFlag('authOverviewPage') - - const { - authenticationSignInProviders, - authenticationRateLimits, - authenticationEmails, - authenticationMultiFactor, - authenticationAttackProtection, - authenticationPerformance, - } = useIsFeatureEnabled([ - 'authentication:sign_in_providers', - 'authentication:rate_limits', - 'authentication:emails', - 'authentication:multi_factor', - 'authentication:attack_protection', - 'authentication:performance', - ]) +export interface GenerateAuthMenuOptions { + ref?: string + isPlatform: boolean + showOverview: boolean + features: { + signInProviders: boolean + rateLimits: boolean + emails: boolean + multiFactor: boolean + attackProtection: boolean + performance: boolean + } +} +export function generateAuthMenu(options: GenerateAuthMenuOptions): ProductMenuGroup[] { + const { ref, isPlatform, showOverview, features } = options const baseUrl = `/project/${ref}/auth` return [ { title: 'Manage', items: [ - ...(authenticationShowOverview + ...(showOverview ? [{ name: 'Overview', key: 'overview', url: `${baseUrl}/overview`, items: [] }] : []), { name: 'Users', key: 'users', url: `${baseUrl}/users`, items: [] }, - { - name: 'OAuth Apps', - key: 'oauth-apps', - url: `${baseUrl}/oauth-apps`, - items: [], - }, + ...(isPlatform + ? [ + { + name: 'OAuth Apps', + key: 'oauth-apps', + url: `${baseUrl}/oauth-apps`, + items: [], + }, + ] + : []), ], }, - ...(authenticationEmails && IS_PLATFORM + ...(features.emails && isPlatform ? [ { title: 'Notifications', items: [ - ...(authenticationEmails + ...(features.emails ? [ { name: 'Email', @@ -71,9 +71,9 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { url: `${baseUrl}/policies`, items: [], }, - ...(IS_PLATFORM + ...(isPlatform ? [ - ...(authenticationSignInProviders + ...(features.signInProviders ? [ { name: 'Sign In / Providers', @@ -96,7 +96,7 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { url: `${baseUrl}/sessions`, items: [], }, - ...(authenticationRateLimits + ...(features.rateLimits ? [ { name: 'Rate Limits', @@ -106,7 +106,7 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { }, ] : []), - ...(authenticationMultiFactor + ...(features.multiFactor ? [ { name: 'Multi-Factor', @@ -122,7 +122,7 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { url: `${baseUrl}/url-configuration`, items: [], }, - ...(authenticationAttackProtection + ...(features.attackProtection ? [ { name: 'Attack Protection', @@ -145,7 +145,7 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { url: `${baseUrl}/audit-logs`, items: [], }, - ...(authenticationPerformance + ...(features.performance ? [ { name: 'Performance', @@ -161,3 +161,38 @@ export const useGenerateAuthMenu = (): ProductMenuGroup[] => { }, ] } + +export const useGenerateAuthMenu = (): ProductMenuGroup[] => { + const { ref } = useParams() + const showOverview = useFlag('authOverviewPage') + + const { + authenticationSignInProviders, + authenticationRateLimits, + authenticationEmails, + authenticationMultiFactor, + authenticationAttackProtection, + authenticationPerformance, + } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + 'authentication:rate_limits', + 'authentication:emails', + 'authentication:multi_factor', + 'authentication:attack_protection', + 'authentication:performance', + ]) + + return generateAuthMenu({ + ref, + isPlatform: IS_PLATFORM, + showOverview, + features: { + signInProviders: authenticationSignInProviders, + rateLimits: authenticationRateLimits, + emails: authenticationEmails, + multiFactor: authenticationMultiFactor, + attackProtection: authenticationAttackProtection, + performance: authenticationPerformance, + }, + }) +}