fix(next): infinite loop when logging into root admin (#7412)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
})
|
||||
|
||||
BIN
test/admin-root/app/favicon.ico
Normal file
BIN
test/admin-root/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`
|
||||
})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}: {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/admin/config.ts"
|
||||
"./test/_community/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
|
||||
Reference in New Issue
Block a user