fix(next): infinite loop when logging into root admin (#7412)

This commit is contained in:
Jacob Fletcher
2024-07-29 12:57:57 -04:00
committed by GitHub
parent 7ed6634bc5
commit 874279c530
12 changed files with 82 additions and 36 deletions

View File

@@ -7,7 +7,7 @@ import type {
import { notFound } from 'next/navigation.js'
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
export const handleAdminPage = ({
adminRoute,
@@ -20,9 +20,9 @@ export const handleAdminPage = ({
permissions: Permissions
route: string
}) => {
if (isAdminRoute(route, adminRoute)) {
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
if (isAdminRoute({ adminRoute, config, route })) {
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
const [entityType, entitySlug, createOrID] = routeSegments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
@@ -47,7 +47,7 @@ export const handleAdminPage = ({
}
}
if (!permissions.canAccessAdmin && !isAdminAuthRoute(config, route, adminRoute)) {
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
notFound()
}

View File

@@ -22,7 +22,7 @@ export const handleAuthRedirect = ({
routes: { admin: adminRoute },
} = config
if (!isAdminAuthRoute(config, route, adminRoute)) {
if (!isAdminAuthRoute({ adminRoute, config, route })) {
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
const redirectRoute = encodeURIComponent(
@@ -36,7 +36,7 @@ export const handleAuthRedirect = ({
const customLoginRoute =
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
const loginRoute = isAdminRoute(route, adminRoute)
const loginRoute = isAdminRoute({ adminRoute, config, route })
? adminLoginRoute
: customLoginRoute || loginRouteFromConfig

View File

@@ -11,16 +11,42 @@ const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
'reset',
]
export const isAdminRoute = (route: string, adminRoute: string) => {
return route.startsWith(adminRoute)
export const isAdminRoute = ({
adminRoute,
config,
route,
}: {
adminRoute: string
config: SanitizedConfig
route: string
}): boolean => {
return route.startsWith(adminRoute) && !isAdminAuthRoute({ adminRoute, config, route })
}
export const isAdminAuthRoute = (config: SanitizedConfig, route: string, adminRoute: string) => {
export const isAdminAuthRoute = ({
adminRoute,
config,
route,
}: {
adminRoute: string
config: SanitizedConfig
route: string
}): boolean => {
const authRoutes = config.admin?.routes
? Object.entries(config.admin.routes)
.filter(([key]) => authRouteKeys.includes(key as keyof SanitizedConfig['admin']['routes']))
.map(([_, value]) => value)
: []
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
return authRoutes.some((r) => getRouteWithoutAdmin({ adminRoute, route }).startsWith(r))
}
export const getRouteWithoutAdmin = ({
adminRoute,
route,
}: {
adminRoute: string
route: string
}): string => {
return adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
}

View File

@@ -18,7 +18,7 @@ import {
closeNav,
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
login,
openDocControls,
@@ -99,7 +99,7 @@ describe('access control', () => {
routes: { logout: logoutRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({})
} = getRoutes({})
logoutURL = `${serverURL}${adminRoute}${logoutRoute}`
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -12,6 +12,13 @@ const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
collections: [PostsCollection],
admin: {
autoLogin: {
email: devUser.email,
password: devUser.password,
prefillOnly: true,
},
},
cors: ['http://localhost:3000', 'http://localhost:3001'],
globals: [MenuGlobal],
routes: {

View File

@@ -5,7 +5,7 @@ import * as path from 'path'
import { adminRoute } from 'shared.js'
import { fileURLToPath } from 'url'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch, login } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
@@ -29,6 +29,8 @@ test.describe('Admin Panel (Root)', () => {
page = await context.newPage()
initPageConsoleErrorCatch(page)
await login({ page, serverURL, customRoutes: { admin: adminRoute } })
await ensureCompilationIsDone({
customRoutes: {
admin: adminRoute,

View File

@@ -10,7 +10,7 @@ import {
checkPageTitle,
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
openDocControls,
openNav,
@@ -76,7 +76,7 @@ describe('admin1', () => {
let customFieldsURL: AdminUrlUtil
let disableDuplicateURL: AdminUrlUtil
let serverURL: string
let adminRoutes: ReturnType<typeof getAdminRoutes>
let adminRoutes: ReturnType<typeof getRoutes>
let loginURL: string
beforeAll(async ({ browser }, testInfo) => {
@@ -106,7 +106,7 @@ describe('admin1', () => {
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
adminRoutes = getAdminRoutes({ customAdminRoutes })
adminRoutes = getRoutes({ customAdminRoutes })
loginURL = `${serverURL}${adminRoutes.routes.admin}${adminRoutes.admin.routes.login}`
})

View File

@@ -10,7 +10,7 @@ import type { Config, Geo, Post } from '../../payload-types.js'
import {
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
openDocDrawer,
openNav,
@@ -44,7 +44,7 @@ describe('admin2', () => {
let postsUrl: AdminUrlUtil
let serverURL: string
let adminRoutes: ReturnType<typeof getAdminRoutes>
let adminRoutes: ReturnType<typeof getRoutes>
beforeAll(async ({ browser }, testInfo) => {
const prebuild = Boolean(process.env.CI)
@@ -69,7 +69,7 @@ describe('admin2', () => {
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
adminRoutes = getAdminRoutes({ customAdminRoutes })
adminRoutes = getRoutes({ customAdminRoutes })
})
beforeEach(async () => {
await reInitializeDB({

View File

@@ -13,7 +13,7 @@ import type { Config } from './payload-types.js'
import {
ensureCompilationIsDone,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
saveDocAndAssert,
} from '../helpers.js'
@@ -49,7 +49,7 @@ const createFirstUser = async ({
routes: { createFirstUser: createFirstUserRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({
} = getRoutes({
customAdminRoutes,
customRoutes,
})

View File

@@ -1,6 +1,7 @@
import type { BrowserContext, ChromiumBrowserContext, Locator, Page } from '@playwright/test'
import type { Config } from 'payload'
import { formatAdminURL } from '@payloadcms/ui/shared'
import { expect } from '@playwright/test'
import { defaults } from 'payload'
import { wait } from 'payload/shared'
@@ -65,7 +66,7 @@ export async function ensureCompilationIsDone({
}): Promise<void> {
const {
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
} = getRoutes({ customAdminRoutes, customRoutes })
const adminURL = `${serverURL}${adminRoute}`
@@ -114,7 +115,7 @@ export async function firstRegister(args: FirstRegisterArgs): Promise<void> {
const {
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
} = getRoutes({ customAdminRoutes, customRoutes })
await page.goto(`${serverURL}${adminRoute}`)
await page.fill('#field-email', devUser.email)
@@ -130,27 +131,37 @@ export async function login(args: LoginArgs): Promise<void> {
const {
admin: {
routes: { createFirstUser: createFirstUserRoute, login: loginRoute },
routes: { createFirstUser, login: incomingLoginRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
routes: { admin: incomingAdminRoute },
} = getRoutes({ customAdminRoutes, customRoutes })
await page.goto(`${serverURL}${adminRoute}${loginRoute}`)
await page.waitForURL(`${serverURL}${adminRoute}${loginRoute}`)
const adminRoute = formatAdminURL({ serverURL, adminRoute: incomingAdminRoute, path: '' })
const loginRoute = formatAdminURL({
serverURL,
adminRoute: incomingAdminRoute,
path: incomingLoginRoute,
})
const createFirstUserRoute = formatAdminURL({
serverURL,
adminRoute: incomingAdminRoute,
path: createFirstUser,
})
await page.goto(loginRoute)
await page.waitForURL(loginRoute)
await wait(500)
await page.fill('#field-email', data.email)
await page.fill('#field-password', data.password)
await wait(500)
await page.click('[type=submit]')
await page.waitForURL(`${serverURL}${adminRoute}`)
await page.waitForURL(adminRoute)
await expect(() => expect(page.url()).not.toContain(`${adminRoute}${loginRoute}`)).toPass({
await expect(() => expect(page.url()).not.toContain(loginRoute)).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(() =>
expect(page.url()).not.toContain(`${adminRoute}${createFirstUserRoute}`),
).toPass({
await expect(() => expect(page.url()).not.toContain(createFirstUserRoute)).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
}
@@ -328,7 +339,7 @@ export function describeIfInCIOrHasLocalstack(): jest.Describe {
type AdminRoutes = Config['admin']['routes']
export function getAdminRoutes({
export function getRoutes({
customAdminRoutes,
customRoutes,
}: {

View File

@@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/admin/config.ts"
"./test/_community/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"