diff --git a/packages/next/src/views/Edit/Default/index.tsx b/packages/next/src/views/Edit/Default/index.tsx index 7ccab1e05..b9a995826 100644 --- a/packages/next/src/views/Edit/Default/index.tsx +++ b/packages/next/src/views/Edit/Default/index.tsx @@ -66,6 +66,7 @@ export const DefaultEditView: React.FC = () => { const { reportUpdate } = useDocumentEvents() const { + admin: { user: userSlug }, collections, globals, routes: { admin: adminRoute, api: apiRoute }, @@ -108,9 +109,9 @@ export const DefaultEditView: React.FC = () => { updatedAt: json?.result?.updatedAt || new Date().toISOString(), }) - // If we're editing the doc of the logged in user, + // If we're editing the doc of the logged-in user, // Refresh the cookie to get new permissions - if (user && collectionSlug === user?.collection && id === user?.id) { + if (user && collectionSlug === userSlug && id === user.id) { void refreshCookieAsync() } @@ -135,8 +136,7 @@ export const DefaultEditView: React.FC = () => { id, entitySlug, collectionSlug, - user?.collection, - user?.id, + user, getVersions, getDocPermissions, isEditing, diff --git a/packages/payload/src/auth/operations/me.ts b/packages/payload/src/auth/operations/me.ts index 2681927e1..727cf80cb 100644 --- a/packages/payload/src/auth/operations/me.ts +++ b/packages/payload/src/auth/operations/me.ts @@ -2,13 +2,13 @@ import jwt from 'jsonwebtoken' import type { Collection } from '../../collections/config/types.js' import type { PayloadRequest } from '../../types/index.js' -import type { User } from '../types.js' +import type { ClientUser, User } from '../types.js' -export type Result = { +export type MeOperationResult = { collection?: string exp?: number token?: string - user?: User + user?: ClientUser } export type Arguments = { @@ -21,8 +21,8 @@ export const meOperation = async ({ collection, currentToken, req, -}: Arguments): Promise => { - let result: Result = { +}: Arguments): Promise => { + let result: MeOperationResult = { user: null, } diff --git a/packages/payload/src/auth/types.ts b/packages/payload/src/auth/types.ts index 81b3a1f82..bb7f04c3e 100644 --- a/packages/payload/src/auth/types.ts +++ b/packages/payload/src/auth/types.ts @@ -62,12 +62,18 @@ export type Permissions = { } export type User = { - [key: string]: unknown + [key: string]: any // This NEEDS to be an any, otherwise it breaks the Omit for ClientUser below collection: string email: string id: string } +/** + * `collection` is not available one the client. It's only available on the server (req.user) + * On the client, you can access the collection via config.admin.user. Config can be accessed using the useConfig() hook + */ +export type ClientUser = Omit + type GenerateVerifyEmailHTML = (args: { req: PayloadRequest token: string diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 7176b12eb..bfb5eeb4d 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -7,7 +7,7 @@ import type { CustomSaveButtonProps, CustomSaveDraftButtonProps, } from '../../admin/types.js' -import type { Auth, IncomingAuthType, User } from '../../auth/types.js' +import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js' import type { Access, EditConfig, @@ -270,7 +270,7 @@ export type CollectionAdminOptions = { /** * Exclude the collection from the admin nav and routes */ - hidden?: ((args: { user: User }) => boolean) | boolean + hidden?: ((args: { user: ClientUser }) => boolean) | boolean /** * Hide the API URL within the Edit view */ diff --git a/packages/payload/src/exports/types.ts b/packages/payload/src/exports/types.ts index d30136eec..62acd9a75 100644 --- a/packages/payload/src/exports/types.ts +++ b/packages/payload/src/exports/types.ts @@ -3,6 +3,7 @@ export type * from '../admin/types.js' export type * from '../uploads/types.js' export type { DocumentPermissions, FieldPermissions } from '../auth/index.js' +export type { MeOperationResult } from '../auth/operations/me.js' export type { CollapsedPreferences, diff --git a/packages/richtext-lexical/src/field/features/relationship/utils/EnabledRelationshipsCondition.tsx b/packages/richtext-lexical/src/field/features/relationship/utils/EnabledRelationshipsCondition.tsx index 36a992169..a1556efef 100644 --- a/packages/richtext-lexical/src/field/features/relationship/utils/EnabledRelationshipsCondition.tsx +++ b/packages/richtext-lexical/src/field/features/relationship/utils/EnabledRelationshipsCondition.tsx @@ -1,4 +1,4 @@ -import type { User } from 'payload/auth' +import type { ClientUser } from 'payload/auth' import type { SanitizedCollectionConfig } from 'payload/types' import { useAuth, useConfig } from '@payloadcms/ui' @@ -6,7 +6,7 @@ import * as React from 'react' type options = { uploads: boolean - user: User + user: ClientUser } type FilteredCollectionsT = ( diff --git a/packages/richtext-slate/src/field/elements/EnabledRelationshipsCondition.tsx b/packages/richtext-slate/src/field/elements/EnabledRelationshipsCondition.tsx index 4f11446a9..d80acbcf8 100644 --- a/packages/richtext-slate/src/field/elements/EnabledRelationshipsCondition.tsx +++ b/packages/richtext-slate/src/field/elements/EnabledRelationshipsCondition.tsx @@ -1,6 +1,6 @@ 'use client' -import type { User } from 'payload/auth' +import type { ClientUser } from 'payload/auth' import type { SanitizedCollectionConfig } from 'payload/types' import { useAuth, useConfig } from '@payloadcms/ui/providers' @@ -8,7 +8,7 @@ import * as React from 'react' type options = { uploads: boolean - user: User + user: ClientUser } type FilteredCollectionsT = ( diff --git a/packages/ui/src/forms/Label/index.tsx b/packages/ui/src/forms/Label/index.tsx index 35d8f5f8c..a6e7f4567 100644 --- a/packages/ui/src/forms/Label/index.tsx +++ b/packages/ui/src/forms/Label/index.tsx @@ -11,17 +11,17 @@ import { useForm } from '../Form/context.js' import './index.scss' const Label: React.FC = (props) => { - const { htmlFor: htmlForFromProps, label, required = false } = props + const { htmlFor: htmlForFromProps, label: labelFromProps, required = false } = props const { uuid } = useForm() const { path } = useFieldProps() const htmlFor = htmlForFromProps || generateFieldID(path, uuid) const { i18n } = useTranslation() - if (label) { + if (labelFromProps) { return ( ) diff --git a/packages/ui/src/providers/Auth/index.tsx b/packages/ui/src/providers/Auth/index.tsx index eea3ebb01..8f425e6be 100644 --- a/packages/ui/src/providers/Auth/index.tsx +++ b/packages/ui/src/providers/Auth/index.tsx @@ -1,5 +1,6 @@ 'use client' -import type { Permissions, User } from 'payload/auth' +import type { ClientUser, Permissions } from 'payload/auth' +import type { MeOperationResult } from 'payload/types.js' import * as facelessUIImport from '@faceless-ui/modal' import { usePathname, useRouter } from 'next/navigation.js' @@ -24,7 +25,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const { useModal } = facelessUIImport const { searchParams } = useSearchParams() - const [user, setUser] = useState() + const [user, setUser] = useState() const [tokenInMemory, setTokenInMemory] = useState() const [tokenExpiration, setTokenExpiration] = useState() const pathname = usePathname() @@ -94,6 +95,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (request.status === 200) { const json = await request.json() setUser(json.user) + setTokenAndExpiration(json) } else { setUser(null) @@ -117,7 +119,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children ) const refreshCookieAsync = useCallback( - async (skipSetUser?: boolean): Promise => { + async (skipSetUser?: boolean): Promise => { try { const request = await requests.post(`${serverURL}${api}/${userSlug}/refresh-token`, { headers: { @@ -187,10 +189,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) if (request.status === 200) { - const json = await request.json() + const json: MeOperationResult = await request.json() if (json?.user) { setUser(json.user) + if (json?.token) { setTokenAndExpiration(json) } @@ -317,4 +320,4 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children ) } -export const useAuth = (): AuthContext => useContext(Context) as AuthContext +export const useAuth = (): AuthContext => useContext(Context) as AuthContext diff --git a/packages/ui/src/providers/Auth/types.ts b/packages/ui/src/providers/Auth/types.ts index 071a0d1c5..8f1f04202 100644 --- a/packages/ui/src/providers/Auth/types.ts +++ b/packages/ui/src/providers/Auth/types.ts @@ -1,11 +1,11 @@ -import type { Permissions, User } from 'payload/auth' +import type { ClientUser, Permissions } from 'payload/auth' -export type AuthContext = { +export type AuthContext = { fetchFullUser: () => Promise logOut: () => void permissions?: Permissions refreshCookie: (forceRefresh?: boolean) => void - refreshCookieAsync: () => Promise + refreshCookieAsync: () => Promise refreshPermissions: () => Promise setPermissions: (permissions: Permissions) => void setUser: (user: T) => void diff --git a/test/auth/config.ts b/test/auth/config.ts index 44e14ac21..a48ce8761 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -17,6 +17,9 @@ export default buildConfigWithDefaults({ }, collections: [ { + admin: { + useAsTitle: 'custom', + }, slug, auth: { cookies: { @@ -198,6 +201,16 @@ export default buildConfigWithDefaults({ }, fields: [], }, + { + slug: 'relationsCollection', + fields: [ + { + name: 'rel', + type: 'relationship', + relationTo: 'users', + }, + ], + }, ], onInit: async (payload) => { await payload.create({ diff --git a/test/auth/e2e.spec.ts b/test/auth/e2e.spec.ts index 8b2b78a65..0ea953078 100644 --- a/test/auth/e2e.spec.ts +++ b/test/auth/e2e.spec.ts @@ -4,7 +4,8 @@ import { expect, test } from '@playwright/test' import path from 'path' import { fileURLToPath } from 'url' -import payload from '../../packages/payload/src/index.js' +import type { Payload } from '../../packages/payload/src/index.js' + import { initPageConsoleErrorCatch, login, saveDocAndAssert } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' import { initPayloadE2E } from '../helpers/initPayloadE2E.js' @@ -12,6 +13,7 @@ import config from './config.js' import { apiKeysSlug, slug } from './shared.js' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) +let payload: Payload /** * TODO: Auth @@ -33,7 +35,8 @@ describe('auth', () => { let apiURL: string beforeAll(async ({ browser }) => { - ;({ serverURL } = await initPayloadE2E({ config, dirname })) + ;({ serverURL, payload } = await initPayloadE2E({ config, dirname })) + apiURL = `${serverURL}/api` url = new AdminUrlUtil(serverURL, slug) const context = await browser.newContext()