feat(3.0): next route handlers (#4590)

This commit is contained in:
Jarrod Flesch
2023-12-21 16:54:20 -05:00
committed by GitHub
parent a7172d8782
commit 988a21e94d
167 changed files with 1672 additions and 1947 deletions

View File

@@ -1,6 +1,4 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */ /* DO NOT MODIFY it because it could be re-written at any time. */
import { me } from '@payloadcms/next/routes/me'
import config from 'payload-config'
export const GET = me({ config }) export { GET, POST, DELETE, PATCH } from '@payloadcms/next/dist/routes/index'

View File

@@ -1,6 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { init } from '@payloadcms/next/routes/init'
import config from 'payload-config'
export const GET = init({ config })

View File

@@ -1,6 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import { login } from '@payloadcms/next/routes/login'
import config from 'payload-config'
export const POST = login({ config })

15
packages/next/.swcrc Normal file
View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": "inline",
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
}
},
"module": {
"type": "commonjs"
}
}

View File

@@ -30,18 +30,24 @@
"import": "./src/routes/*.ts", "import": "./src/routes/*.ts",
"require": "./src/routes/*.ts", "require": "./src/routes/*.ts",
"types": "./src/routes/*.ts" "types": "./src/routes/*.ts"
} },
"./dist/routes/*": "./dist/routes/*.js"
}, },
"devDependencies": { "devDependencies": {
"@payloadcms/ui": "workspace:*",
"@payloadcms/eslint-config": "workspace:*", "@payloadcms/eslint-config": "workspace:*",
"@payloadcms/ui": "workspace:*",
"payload": "workspace:*", "payload": "workspace:*",
"sass": "^1.69.5" "sass": "^1.69.5"
}, },
"dependencies": {
"jsonwebtoken": "9.0.1",
"path-to-regexp": "^6.2.1"
},
"peerDependencies": { "peerDependencies": {
"payload": "^2.0.0", "http-status": "1.6.2",
"i18next": "22.5.1",
"next": "^14.0.0", "next": "^14.0.0",
"i18next": "22.5.1" "payload": "^2.0.0"
}, },
"publishConfig": { "publishConfig": {
"exports": { "exports": {
@@ -60,6 +66,11 @@
"require": "./dist/pages/*.js", "require": "./dist/pages/*.js",
"types": "./dist/pages/*.d.ts" "types": "./dist/pages/*.d.ts"
}, },
"./app/*": {
"import": "./dist/app/*.js",
"require": "./dist/app/*.js",
"types": "./dist/app/*.d.ts"
},
"./routes/*": { "./routes/*": {
"import": "./dist/routes/*.js", "import": "./dist/routes/*.js",
"require": "./dist/routes/*.js", "require": "./dist/routes/*.js",

View File

@@ -1,6 +1,4 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */ /* DO NOT MODIFY it because it could be re-written at any time. */
import { me } from '@payloadcms/next/routes/me'
import config from 'payload-config'
export const GET = me({ config }) export { GET, POST, DELETE, PATCH } from '@payloadcms/next/dist/routes/index'

View File

@@ -1,6 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import { init } from '@payloadcms/next/routes/init'
import config from 'payload-config'
export const GET = init({ config })

View File

@@ -1,6 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import { login } from '@payloadcms/next/routes/login'
import config from 'payload-config'
export const POST = login({ config })

View File

@@ -1,9 +0,0 @@
import { PayloadRequest, SanitizedConfig } from 'payload/types'
type Args = {
config: SanitizedConfig
req: PayloadRequest
}
export const authenticate = async ({ config, req }: Args): Promise<PayloadRequest> => {
return req
}

View File

@@ -1,2 +0,0 @@
export { init } from '../routes/init'
export { login } from '../routes/login'

View File

@@ -0,0 +1,14 @@
import httpStatus from 'http-status'
import { accessOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
// TODO(JARROD): pattern to catch errors and return correct Response
export const access = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const results = await accessOperation({
req,
})
return Response.json(results, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,25 @@
import { forgotPasswordOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
import httpStatus from 'http-status'
// TODO(JARROD): pattern to catch errors and return correct Response
export const forgotPassword = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
await forgotPasswordOperation({
collection: req.collection,
data: {
email: req.data.email as string,
},
disableEmail: Boolean(req.data?.disableEmail),
expiration: typeof req.data.expiration === 'number' ? req.data.expiration : undefined,
req,
})
return Response.json(
{
message: 'Success',
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,11 @@
import type { PayloadRequest } from 'payload/types'
import { init as initOperation } from 'payload/operations'
export const init = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const initialized = await initOperation({
collection: req.collection.config.slug,
req,
})
return Response.json({ initialized })
}

View File

@@ -0,0 +1,42 @@
import httpStatus from 'http-status'
import { loginOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { generatePayloadCookie } from '../../utilities/cookies'
// TODO(JARROD): pattern to catch errors and return correct Response
export const login = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await loginOperation({
collection: req.collection,
data: {
email: typeof req.data?.email === 'string' ? req.data.email : '',
password: typeof req.data?.password === 'string' ? req.data.password : '',
},
depth: isNumber(depth) ? Number(depth) : undefined,
req,
})
const cookie = generatePayloadCookie({
token: result.token,
payload: req.payload,
collectionConfig: req.collection.config,
})
return Response.json(
{
exp: result.exp,
message: 'Auth Passed',
token: result.token,
user: result.user,
},
{
headers: new Headers({
'Set-Cookie': cookie,
}),
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,40 @@
import httpStatus from 'http-status'
import { logoutOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
import { generateExpiredPayloadCookie } from '../../utilities/cookies'
// TODO(JARROD): pattern to catch errors and return correct Response
export const logout = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const result = logoutOperation({
collection: req.collection,
req,
})
if (!result) {
return Response.json(
{
message: 'Logout failed.',
},
{
status: httpStatus.BAD_REQUEST,
},
)
}
const expiredCookie = generateExpiredPayloadCookie({
collectionConfig: req.collection.config,
payload: req.payload,
})
return Response.json(
{
message: 'Logout successful.',
},
{
headers: new Headers({
'Set-Cookie': expiredCookie,
}),
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,25 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { meOperation } from 'payload/operations'
import { extractJWT } from '../../utilities/jwt'
// TODO(JARROD): pattern to catch errors and return correct Response
export const me = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const currentToken = extractJWT(req)
const result = await meOperation({
collection: req.collection,
req,
currentToken,
})
return Response.json(
{
...result,
message: 'Successfully retrieved me user.',
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,48 @@
import { extractJWT } from '../../utilities/jwt'
import { refreshOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
import httpStatus from 'http-status'
import { generatePayloadCookie } from '../../utilities/cookies'
// TODO(JARROD): pattern to catch errors and return correct Response
export const refresh = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const token = typeof req.data?.token === 'string' ? req.data.token : extractJWT(req)
if (!token) {
return Response.json(
{
message: 'Token not provided.',
},
{
status: httpStatus.UNAUTHORIZED,
},
)
}
const result = await refreshOperation({
token,
req,
collection: req.collection,
})
const cookie = generatePayloadCookie({
token: result.refreshedToken,
payload: req.payload,
collectionConfig: req.collection.config,
})
return Response.json(
{
exp: result.exp,
message: 'Token refresh successful',
token: result.refreshedToken,
user: result.user,
},
{
headers: new Headers({
'Set-Cookie': cookie,
}),
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,37 @@
import httpStatus from 'http-status'
import { registerFirstUserOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
import { generatePayloadCookie } from '../../utilities/cookies'
// TODO(JARROD): pattern to catch errors and return correct Response
export const registerFirstUser = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const result = await registerFirstUserOperation({
collection: req.collection,
data: {
email: typeof req.data?.email === 'string' ? req.data.email : '',
password: typeof req.data?.password === 'string' ? req.data.password : '',
},
req,
})
const cookie = generatePayloadCookie({
token: result.token,
payload: req.payload,
collectionConfig: req.collection.config,
})
return Response.json(
{
exp: result.exp,
message: 'Successfully registered first user.',
token: result.token,
user: result.user,
},
{
headers: new Headers({
'Set-Cookie': cookie,
}),
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,41 @@
import httpStatus from 'http-status'
import { resetPasswordOperation } from 'payload/operations'
import { generatePayloadCookie } from '../../utilities/cookies'
import { PayloadRequest } from 'payload/types'
// TODO(JARROD): pattern to catch errors and return correct Response
export const resetPassword = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await resetPasswordOperation({
collection: req.collection,
data: {
password: typeof req.data?.password === 'string' ? req.data.password : '',
token: typeof req.data?.token === 'string' ? req.data.token : '',
},
depth: depth ? Number(depth) : undefined,
req,
})
const cookie = generatePayloadCookie({
token: result.token,
payload: req.payload,
collectionConfig: req.collection.config,
})
return Response.json(
{
message: 'Password reset successfully.',
token: result.token,
user: result.user,
},
{
headers: new Headers({
'Set-Cookie': cookie,
}),
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,22 @@
import httpStatus from 'http-status'
import { unlockOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
// TODO(JARROD): pattern to catch errors and return correct Response
export const unlock = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
await unlockOperation({
collection: req.collection,
data: { email: req.data.email as string },
req,
})
return Response.json(
{
message: 'Success',
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,27 @@
import httpStatus from 'http-status'
import { verifyEmailOperation } from 'payload/operations'
import { PayloadRequest } from 'payload/types'
export const verifyEmail = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
await verifyEmailOperation({
collection: req.collection,
req,
token: id,
})
return Response.json(
{
message: 'Email verified successfully.',
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,33 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { createOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const create = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true'
const depth = searchParams.get('depth')
const doc = await createOperation({
autosave,
collection: req.collection,
data: req.data,
depth: isNumber(depth) ? depth : undefined,
draft,
req,
})
// ...formatSuccessResponse(
// req.t('general:successfullyCreated', {
// label: getTranslation(req.collection.config.labels.singular, req.i18n),
// }),
// 'message',
// )
return Response.json(doc, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,62 @@
import httpStatus from 'http-status'
import type { PayloadRequest, Where } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { deleteOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const deleteDoc = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const where = searchParams.get('where')
const result = await deleteOperation({
collection: req.collection,
depth: isNumber(depth) ? depth : undefined,
req,
where: where ? (JSON.parse(where) as Where) : {},
})
if (result.errors.length === 0) {
// const message = req.t('general:deletedCountSuccessfully', {
// count: result.docs.length,
// label: getTranslation(
// req.collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
// req.i18n,
// ),
// })
// res.status(httpStatus.OK).json({
// ...formatSuccessResponse(message, 'message'),
// ...result,
// })
return Response.json(result, {
status: httpStatus.OK,
})
}
// const total = result.docs.length + result.errors.length
// const message = req.t('error:unableToDeleteCount', {
// count: result.errors.length,
// label: getTranslation(
// req.collection.config.labels[total > 1 ? 'plural' : 'singular'],
// req.i18n,
// ),
// total,
// })
// res.status(httpStatus.BAD_REQUEST).json({
// message,
// ...result,
// })
return Response.json(
{
...result,
},
{
status: httpStatus.BAD_REQUEST,
},
)
}

View File

@@ -0,0 +1,45 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { deleteByIDOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const deleteByID = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const doc = await deleteByIDOperation({
id,
collection: req.collection,
depth: isNumber(depth) ? depth : undefined,
req,
})
if (!doc) {
return Response.json(
{
message: 'Not Found',
},
{
status: httpStatus.NOT_FOUND,
},
)
}
return Response.json(
{
...doc,
// ...formatSuccessResponse(req.t('general:successfullyDeleted'), 'message'),
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,23 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { docAccessOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const docAccess = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const result = await docAccessOperation({
id,
req,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,31 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { findOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const find = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const limit = searchParams.get('limit')
const page = searchParams.get('page')
const where = searchParams.get('where')
const result = await findOperation({
collection: req.collection,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort: searchParams.get('sort'),
where: where ? JSON.parse(where) : undefined,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,29 @@
import httpStatus from 'http-status'
import { PayloadRequest } from 'payload/types'
import { findByIDOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findByID = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await findByIDOperation({
id,
collection: req.collection,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
req,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,28 @@
import httpStatus from 'http-status'
import { PayloadRequest } from 'payload/types'
import { findVersionByIDOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findVersionByID = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await findVersionByIDOperation({
id,
collection: req.collection,
depth: isNumber(depth) ? Number(depth) : undefined,
req,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,35 @@
import httpStatus from 'http-status'
import { PayloadRequest, Where } from 'payload/types'
import { findVersionsOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findVersions = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const page = searchParams.get('page')
const depth = searchParams.get('depth')
const limit = searchParams.get('limit')
const where = searchParams.get('where')
const sort = searchParams.get('sort')
const result = await findVersionsOperation({
collection: req.collection,
depth: isNumber(depth) ? Number(depth) : undefined,
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort: sort,
where: where ? (JSON.parse(where) as Where) : undefined,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,34 @@
import httpStatus from 'http-status'
import { PayloadRequest } from 'payload/types'
import { restoreVersionOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const restoreVersion = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await restoreVersionOperation({
id,
collection: req.collection,
depth: isNumber(depth) ? Number(depth) : undefined,
req,
})
return Response.json(
{
...result,
// ...formatSuccessResponse(req.t('version:restoredSuccessfully'), 'message'),
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,63 @@
import httpStatus from 'http-status'
import type { PayloadRequest, Where } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { updateOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const update = async ({ req }: { req: PayloadRequest }): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const where = searchParams.get('where')
const result = await updateOperation({
collection: req.collection,
data: req.data,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
req,
where: where ? (JSON.parse(where) as Where) : undefined,
})
if (result.errors.length === 0) {
// const message = req.t('general:updatedCountSuccessfully', {
// count: result.docs.length,
// label: getTranslation(
// req.collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
// req.i18n,
// ),
// })
// res.status(httpStatus.OK).json({
// ...formatSuccessResponse(message, 'message'),
// ...result,
// })
return Response.json(result, {
status: httpStatus.OK,
})
}
// const total = result.docs.length + result.errors.length
// const message = req.t('error:unableToUpdateCount', {
// count: result.errors.length,
// label: getTranslation(
// req.collection.config.labels[total > 1 ? 'plural' : 'singular'],
// req.i18n,
// ),
// total,
// })
// res.status(httpStatus.BAD_REQUEST).json({
// ...formatSuccessResponse(message, 'message'),
// ...result,
// })
return Response.json(
{
...result,
},
{
status: httpStatus.BAD_REQUEST,
},
)
}

View File

@@ -0,0 +1,45 @@
import httpStatus from 'http-status'
import type { PayloadRequest } from 'payload/types'
import { isNumber } from 'payload/utilities'
import { updateByIDOperation } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const updateByID = async ({
req,
id,
}: {
req: PayloadRequest
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const autosave = searchParams.get('autosave') === 'true'
const draft = searchParams.get('draft') === 'true'
const doc = await updateByIDOperation({
id,
autosave,
collection: req.collection,
data: req.data,
depth: isNumber(depth) ? Number(depth) : undefined,
draft,
req,
})
let message = req.t('general:updatedSuccessfully')
if (draft) message = req.t('version:draftSavedSuccessfully')
if (autosave) message = req.t('version:autosavedSuccessfully')
return Response.json(
{
// ...formatSuccessResponse(message, 'message'),
doc,
},
{
status: httpStatus.OK,
},
)
}

View File

@@ -0,0 +1,23 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { docAccessOperationGlobal } from 'payload/operations'
// TODO(JARROD): pattern to catch errors and return correct Response
export const docAccess = async ({
req,
globalConfig,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
}): Promise<Response> => {
const result = await docAccessOperationGlobal({
globalConfig,
req,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,30 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { findOneOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findOne = async ({
req,
globalConfig,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await findOneOperation({
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
globalConfig,
req,
slug: globalConfig.slug,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,31 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { findVersionByIDOperationGlobal } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findVersionByID = async ({
req,
globalConfig,
id,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const result = await findVersionByIDOperationGlobal({
id,
depth: isNumber(depth) ? Number(depth) : undefined,
globalConfig,
req,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,35 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig, Where } from 'payload/types'
import { findVersionsOperationGlobal } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const findVersions = async ({
req,
globalConfig,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const page = searchParams.get('page')
const limit = searchParams.get('limit')
const depth = searchParams.get('depth')
const where = searchParams.get('where')
const result = await findVersionsOperationGlobal({
depth: isNumber(depth) ? Number(depth) : undefined,
globalConfig,
limit: isNumber(limit) ? Number(limit) : undefined,
page: isNumber(page) ? Number(page) : undefined,
req,
sort: searchParams.get('sort'),
where: where ? (JSON.parse(where) as Where) : undefined,
})
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,32 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { restoreVersionOperationGlobal } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const restoreVersion = async ({
req,
globalConfig,
id,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
id: string
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const doc = await restoreVersionOperationGlobal({
id,
depth: isNumber(depth) ? Number(depth) : undefined,
globalConfig,
req,
})
// ...formatSuccessResponse(req.t('version:restoredSuccessfully'), 'message'),
return Response.json(doc, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,39 @@
import httpStatus from 'http-status'
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
import { updateOperationGlobal } from 'payload/operations'
import { isNumber } from 'payload/utilities'
// TODO(JARROD): pattern to catch errors and return correct Response
export const update = async ({
req,
globalConfig,
}: {
req: PayloadRequest
globalConfig: SanitizedGlobalConfig
}): Promise<Response> => {
const { searchParams } = new URL(req.url)
const depth = searchParams.get('depth')
const draft = searchParams.get('draft') === 'true'
const autosave = searchParams.get('autosave') === 'true'
const result = await updateOperationGlobal({
autosave,
data: req.data,
depth: isNumber(depth) ? Number(depth) : undefined,
draft,
globalConfig,
req,
slug: globalConfig.slug,
})
let message = req.t('general:updatedSuccessfully')
if (draft) message = req.t('version:draftSavedSuccessfully')
if (autosave) message = req.t('version:autosavedSuccessfully')
return Response.json(result, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,258 @@
import config from 'payload-config'
import { createPayloadRequest } from '../utilities/createPayloadRequest'
import { me } from './auth/me'
import { init } from './auth/init'
import { login } from './auth/login'
import { unlock } from './auth/unlock'
import { access } from './auth/access'
import { logout } from './auth/logout'
import { refresh } from './auth/refresh'
import { find } from './collections/find'
import { create } from './collections/create'
import { update } from './collections/update'
import { deleteDoc } from './collections/delete'
import { verifyEmail } from './auth/verifyEmail'
import { findByID } from './collections/findByID'
import { docAccess } from './collections/docAccess'
import { resetPassword } from './auth/resetPassword'
import { updateByID } from './collections/updateByID'
import { deleteByID } from './collections/deleteByID'
import { forgotPassword } from './auth/forgotPassword'
import { findVersions } from './collections/findVersions'
import { registerFirstUser } from './auth/registerFirstUser'
import { restoreVersion } from './collections/restoreVersion'
import { findVersionByID } from './collections/findVersionByID'
import { findOne } from './globals/findOne'
import { update as updateGlobal } from './globals/update'
import { docAccess as docAccessGlobal } from './globals/docAccess'
import { findVersions as findVersionsGlobal } from './globals/findVersions'
import { restoreVersion as restoreVersionGlobal } from './globals/restoreVersion'
import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID'
const endpoints = {
root: {
GET: {
access,
},
},
collection: {
GET: {
init,
me,
versions: findVersions,
find,
findByID,
'doc-access-by-id': docAccess,
'doc-versions-by-id': findVersionByID,
},
POST: {
create,
login,
logout,
unlock,
access: docAccess,
'first-register': registerFirstUser,
'forgot-password': forgotPassword,
'reset-password': resetPassword,
'refresh-token': refresh,
'doc-access-by-id': docAccess,
'doc-versions-by-id': restoreVersion,
'doc-verify-by-id': verifyEmail,
},
PATCH: {
update,
updateByID,
},
DELETE: {
delete: deleteDoc,
deleteByID,
},
},
global: {
GET: {
findOne,
'doc-access': docAccessGlobal,
'doc-versions': findVersionsGlobal,
'doc-versions-by-id': findVersionByIdGlobal,
},
POST: {
update: updateGlobal,
'doc-access': docAccessGlobal,
'doc-versions-by-id': restoreVersionGlobal,
},
},
}
export const GET = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2, slug3, slug4] = slug
const req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
if (slug.length === 1 && slug1 === 'access') {
return endpoints.root.GET.access({ req })
}
if (req?.collection) {
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.GET.find({ req })
case 2:
if (slug2 in endpoints.collection.GET) {
// /:collection/init
// /:collection/me
// /:collection/versions
return endpoints.collection.GET?.[slug2]({ req })
} else if (req.collection.config.endpoints && req.collection.config.endpoints.length > 0) {
// /:collection/:id
}
return endpoints.collection.GET.findByID({ req, id: slug2 })
case 3:
// /:collection/access/:id
// /:collection/versions/:id
const key = `doc-${slug2}-by-id`
if (key in endpoints.collection.GET) {
return endpoints.collection.GET[key]({ req, id: slug3 })
}
break
default:
return new Response('Route Not Found', { status: 404 })
}
} else if (slug1 === 'globals') {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
switch (slug.length) {
case 2:
// /globals/:slug
return endpoints.global.GET.findOne({ req, globalConfig })
case 3:
// /globals/:slug/access
// /globals/:slug/versions
return endpoints.global.GET?.[`doc-${slug3}`]({ req, globalConfig })
case 4:
// /globals/:slug/versions/:id
return endpoints.global.GET?.[`doc-${slug3}-by-id`]({ req, id: slug4, globalConfig })
default:
return new Response('Route Not Found', { status: 404 })
}
}
}
export const POST = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2, slug3, slug4] = slug
const req = await createPayloadRequest({ request, config, params: { collection: slug1 } })
if (req?.collection) {
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.POST.create({ req })
case 2:
if (slug2 in endpoints.collection.POST) {
// /:collection/login
// /:collection/logout
// /:collection/unlock
// /:collection/access
// /:collection/first-register
// /:collection/forgot-password
// /:collection/reset-password
// /:collection/refresh-token
return endpoints.collection.POST[slug2]({ req })
}
case 3:
// /:collection/access/:id
// /:collection/versions/:id
// /:collection/verify/:token ("doc-verify-by-id" uses id as token internally)
return endpoints.collection.POST?.[`doc-${slug2}-by-id`]({ req, id: slug3 })
default:
return new Response('Route Not Found', { status: 404 })
}
} else if (slug1 === 'globals') {
const globalConfig = req.payload.config.globals.find((global) => global.slug === slug2)
switch (slug.length) {
case 2:
// /globals/:slug
return endpoints.global.POST.update({ req, globalConfig })
case 3:
// /globals/:slug/access
return endpoints.global.POST?.[`doc-${slug3}`]({ req, globalConfig })
case 4:
// /globals/:slug/versions/:id
return endpoints.global.POST?.[`doc-${slug3}-by-id`]({ req, id: slug4, globalConfig })
default:
return new Response('Route Not Found', { status: 404 })
}
}
}
export const DELETE = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2] = slug
const req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
if (req?.collection) {
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.DELETE.delete({ req })
case 2:
// /:collection/:id
return endpoints.collection.DELETE.deleteByID({ req, id: slug2 })
default:
return new Response('Route Not Found', { status: 404 })
}
}
}
export const PATCH = async (
request: Request,
{ params: { slug } }: { params: { slug: string[] } },
) => {
const [slug1, slug2] = slug
const req = await createPayloadRequest({
request,
config,
params: {
collection: slug1,
},
})
if (req?.collection) {
switch (slug.length) {
case 1:
// /:collection
return endpoints.collection.PATCH.update({ req })
case 2:
// /:collection/:id
return endpoints.collection.PATCH.updateByID({ req, id: slug2 })
default:
return new Response('Route Not Found', { status: 404 })
}
}
}

View File

@@ -1,15 +0,0 @@
import type { SanitizedConfig } from 'payload/types'
import { init as initOperation } from 'payload/operations'
import { createPayloadRequest } from '../createPayloadRequest'
export const init = ({ config }: { config: Promise<SanitizedConfig> }) =>
async function (request: Request, { params }: { params: { collection: string } }) {
const req = await createPayloadRequest({ request, config, params })
const initialized = await initOperation({
collection: params.collection,
req,
})
return Response.json({ initialized })
}

View File

@@ -1,36 +0,0 @@
import { login as loginOperation } from 'payload/operations'
import { createPayloadRequest } from '../createPayloadRequest'
import { SanitizedConfig } from 'payload/types'
import { isNumber } from 'payload/utilities'
export const login = ({ config }: { config: Promise<SanitizedConfig> }) =>
async function (request: Request, { params }: { params: { collection: string } }) {
const req = await createPayloadRequest({ request, config })
const collection = req.payload.collections[params.collection]
const { searchParams } = new URL(request.url)
const depth = searchParams.get('depth')
let responseOptions = {
headers: new Headers(),
}
const result = await loginOperation({
collection,
data: {
email: typeof req.data?.email === 'string' ? req.data.email : '',
password: typeof req.data?.password === 'string' ? req.data.password : '',
},
depth: isNumber(depth) ? Number(depth) : undefined,
req,
responseOptions,
})
return Response.json(
{
exp: result.exp,
message: 'Auth Passed',
token: result.token,
user: result.user,
},
responseOptions,
)
}

View File

@@ -1,15 +0,0 @@
import type { SanitizedConfig } from 'payload/types'
import { me as meOperation } from 'payload/operations'
import { createPayloadRequest } from '../createPayloadRequest'
export const me = ({ config }: { config: Promise<SanitizedConfig> }) =>
async function (request: Request, { params }: { params: { collection: string } }) {
const req = await createPayloadRequest({ request, config })
const collection = req.payload.collections[params.collection]
const meRes = await meOperation({
collection,
req,
})
return Response.json(meRes)
}

View File

@@ -0,0 +1,146 @@
import type { PayloadT, SanitizedCollectionConfig } from 'payload/types'
type CookieOptions = {
domain?: string
expires?: Date
httpOnly?: boolean
maxAge?: number
name: string
path?: string
sameSite?: 'Lax' | 'None' | 'Strict'
secure?: boolean
value?: string
}
export const generateCookies = (cookies: CookieOptions[]): string => {
return cookies.map((options) => generateCookie(options)).join('; ')
}
export const generateCookie = (args: CookieOptions): string => {
const { name, domain, expires, httpOnly, maxAge, path, sameSite, secure: secureArg, value } = args
let cookieString = `${name}=${value || ''}`
const secure = secureArg || sameSite === 'None'
if (expires) {
cookieString += `; Expires=${expires.toUTCString()}`
}
if (maxAge) {
cookieString += `; Max-Age=${maxAge}`
}
if (domain) {
cookieString += `; Domain=${domain}`
}
if (path) {
cookieString += `; Path=${path}`
}
if (secure) {
cookieString += '; Secure'
}
if (httpOnly) {
cookieString += '; HttpOnly'
}
if (sameSite) {
cookieString += `; SameSite=${sameSite}`
}
return cookieString
}
type GetCookieExpirationArgs = {
/*
The number of seconds until the cookie expires
@default 7200 seconds (2 hours)
*/
seconds: number
}
const getCookieExpiration = ({ seconds = 7200 }: GetCookieExpirationArgs) => {
const currentTime = new Date()
currentTime.setSeconds(currentTime.getSeconds() + seconds)
return currentTime
}
type GeneratePayloadCookieArgs = {
/* The auth collection config */
collectionConfig: SanitizedCollectionConfig
/* An instance of payload */
payload: PayloadT
/* The token to be stored in the cookie */
token: string
}
export const generatePayloadCookie = ({
collectionConfig,
payload,
token,
}: GeneratePayloadCookieArgs): string => {
const sameSite =
typeof collectionConfig.auth.cookies.sameSite === 'string'
? collectionConfig.auth.cookies.sameSite
: collectionConfig.auth.cookies.sameSite
? 'Strict'
: undefined
return generateCookie({
name: `${payload.config.cookiePrefix}-token`,
domain: collectionConfig.auth.cookies.domain ?? undefined,
expires: getCookieExpiration({ seconds: collectionConfig.auth.tokenExpiration }),
httpOnly: true,
path: '/',
sameSite,
secure: collectionConfig.auth.cookies.secure,
value: token,
})
}
export const generateExpiredPayloadCookie = ({
collectionConfig,
payload,
}: Omit<GeneratePayloadCookieArgs, 'token'>): string => {
const sameSite =
typeof collectionConfig.auth.cookies.sameSite === 'string'
? collectionConfig.auth.cookies.sameSite
: collectionConfig.auth.cookies.sameSite
? 'Strict'
: undefined
const expires = new Date(Date.now() - 1000)
return generateCookie({
name: `${payload.config.cookiePrefix}-token`,
domain: collectionConfig.auth.cookies.domain ?? undefined,
expires,
httpOnly: true,
path: '/',
sameSite,
secure: collectionConfig.auth.cookies.secure,
})
}
export const parseCookies = (headers: Request['headers']): Map<string, string> => {
const cookieMap = new Map<string, string>()
const cookie = headers.get('Cookie')
if (cookie) {
cookie.split(';').forEach((cookie) => {
const parts = cookie.split('=')
const key = parts.shift().trim()
const encodedValue = parts.join('=')
try {
const decodedValue = decodeURI(encodedValue)
cookieMap.set(key, decodedValue)
} catch (e) {
return null
}
})
}
return cookieMap
}

View File

@@ -0,0 +1,34 @@
import { AuthStrategyFunctionArgs } from 'payload/auth'
import { parseCookies } from './cookies'
export const extractJWT = (
args: Pick<AuthStrategyFunctionArgs, 'headers' | 'payload'>,
): null | string => {
const { headers, payload } = args
const jwtFromHeader = headers.get('Authorization')
const origin = headers.get('Origin')
if (jwtFromHeader?.startsWith('JWT ')) {
return jwtFromHeader.replace('JWT ', '')
}
// allow RFC6750 OAuth 2.0 compliant Bearer tokens
// in addition to the payload default JWT format
if (jwtFromHeader?.startsWith('Bearer ')) {
return jwtFromHeader.replace('Bearer ', '')
}
const cookies = parseCookies(headers)
const tokenCookieName = `${payload.config.cookiePrefix}-token`
const cookieToken = cookies?.get(tokenCookieName)
if (!cookieToken) {
return null
}
if (!origin || payload.config.csrf.length === 0 || payload.config.csrf.indexOf(origin) > -1) {
return cookieToken
}
return null
}

View File

@@ -4,7 +4,7 @@ import type { PayloadRequest } from '../exports/types'
import { Forbidden } from '../errors' import { Forbidden } from '../errors'
type OperationArgs = { type OperationArgs = {
data?: Record<string, unknown> data?: any
disableErrors?: boolean disableErrors?: boolean
id?: number | string id?: number | string
req: PayloadRequest req: PayloadRequest

View File

@@ -1,6 +1,6 @@
import type { AuthStrategyFunctionArgs } from '.' import type { AuthStrategyFunctionArgs } from '.'
import { parseCookies } from '../utilities/cookies' import { parseCookies } from '../utilities/parseCookies'
export const extractJWT = ( export const extractJWT = (
args: Pick<AuthStrategyFunctionArgs, 'headers' | 'payload'>, args: Pick<AuthStrategyFunctionArgs, 'headers' | 'payload'>,

View File

@@ -1,9 +1,9 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import type { User } from '..' import type { User } from '.'
import type { CollectionConfig } from '../../collections/config/types' import type { CollectionConfig } from '../collections/config/types'
import type { Field, TabAsField } from '../../fields/config/types' import type { Field, TabAsField } from '../fields/config/types'
import { fieldAffectsData, tabHasName } from '../../fields/config/types' import { fieldAffectsData, tabHasName } from '../fields/config/types'
type TraverseFieldsArgs = { type TraverseFieldsArgs = {
data: Record<string, unknown> data: Record<string, unknown>

View File

@@ -2,7 +2,7 @@ import type { PayloadT } from '../../..'
import formatName from '../../../graphql/utilities/formatName' import formatName from '../../../graphql/utilities/formatName'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import access from '../../operations/access' import { accessOperation } from '../../operations/access'
const formatConfigNames = (results, configs) => { const formatConfigNames = (results, configs) => {
const formattedResults = { ...results } const formattedResults = { ...results }
@@ -22,7 +22,7 @@ function accessResolver(payload: PayloadT) {
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const accessResults = await access(options) const accessResults = await accessOperation(options)
return { return {
...accessResults, ...accessResults,

View File

@@ -1,5 +1,5 @@
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import init from '../../operations/init' import { init } from '../../operations/init'
function initResolver(collection: string) { function initResolver(collection: string) {
async function resolver(_, args, context) { async function resolver(_, args, context) {

View File

@@ -1,7 +1,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import login from '../../operations/login' import { loginOperation } from '../../operations/login'
function loginResolver(collection: Collection) { function loginResolver(collection: Collection) {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -16,7 +16,7 @@ function loginResolver(collection: Collection) {
res: context.res, res: context.res,
} }
const result = login(options) const result = loginOperation(options)
return result return result
} }

View File

@@ -1,7 +1,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import logout from '../../operations/logout' import { logoutOperation } from '../../operations/logout'
function logoutResolver(collection: Collection): any { function logoutResolver(collection: Collection): any {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -11,7 +11,7 @@ function logoutResolver(collection: Collection): any {
res: context.res, res: context.res,
} }
const result = await logout(options) const result = await logoutOperation(options)
return result return result
} }

View File

@@ -1,7 +1,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import me from '../../operations/me' import { meOperation } from '../../operations/me'
function meResolver(collection: Collection): any { function meResolver(collection: Collection): any {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -10,7 +10,7 @@ function meResolver(collection: Collection): any {
depth: 0, depth: 0,
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
return me(options) return meOperation(options)
} }
return resolver return resolver

View File

@@ -2,7 +2,7 @@ import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import { extractJWT } from '../../getExtractJWT' import { extractJWT } from '../../getExtractJWT'
import refresh from '../../operations/refresh' import { refreshOperation } from '../../operations/refresh'
function refreshResolver(collection: Collection) { function refreshResolver(collection: Collection) {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -22,7 +22,7 @@ function refreshResolver(collection: Collection) {
token, token,
} }
const result = await refresh(options) const result = await refreshOperation(options)
return result return result
} }

View File

@@ -2,7 +2,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import resetPassword from '../../operations/resetPassword' import { resetPasswordOperation } from '../../operations/resetPassword'
function resetPasswordResolver(collection: Collection) { function resetPasswordResolver(collection: Collection) {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -18,7 +18,7 @@ function resetPasswordResolver(collection: Collection) {
res: context.res, res: context.res,
} }
const result = await resetPassword(options) const result = await resetPasswordOperation(options)
return result return result
} }

View File

@@ -1,7 +1,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import unlock from '../../operations/unlock' import { unlockOperation } from '../../operations/unlock'
function unlockResolver(collection: Collection) { function unlockResolver(collection: Collection) {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -11,7 +11,7 @@ function unlockResolver(collection: Collection) {
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await unlock(options) const result = await unlockOperation(options)
return result return result
} }

View File

@@ -2,7 +2,7 @@
import type { Collection } from '../../../collections/config/types' import type { Collection } from '../../../collections/config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import verifyEmail from '../../operations/verifyEmail' import { verifyEmailOperation } from '../../operations/verifyEmail'
function verifyEmailResolver(collection: Collection) { function verifyEmailResolver(collection: Collection) {
async function resolver(_, args, context) { async function resolver(_, args, context) {
@@ -17,7 +17,7 @@ function verifyEmailResolver(collection: Collection) {
token: args.token, token: args.token,
} }
const success = await verifyEmail(options) const success = await verifyEmailOperation(options)
return success return success
} }

View File

@@ -1,24 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import type { Permissions } from '../types'
import access from '../operations/access'
export default async function accessRequestHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<Response<Permissions> | void> {
try {
const accessResults = await access({
req,
})
return res.status(httpStatus.OK).json(accessResults)
} catch (error) {
return next(error)
}
}

View File

@@ -1,30 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import forgotPassword from '../operations/forgotPassword'
export default async function forgotPasswordHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
await forgotPassword({
collection: req.collection,
// TODO(JARROD): remove reliance on express body parsing
data: { email: req.body.email },
disableEmail: req.body.disableEmail,
expiration: req.body.expiration,
req,
})
return res.status(httpStatus.OK).json({
message: 'Success',
})
} catch (error) {
return next(error)
}
}

View File

@@ -1,17 +0,0 @@
import type { SanitizedConfig } from '../../exports/config'
import { getPayload } from '../..'
import init from '../operations/init'
export const initHandler = ({ config }: { config: Promise<SanitizedConfig> }) =>
async function (request: Request, { params }: { params: { collection: string } }) {
const payload = await getPayload({ config })
request.payload = payload
const initialized = await init({
collection: params.collection,
req: request,
})
return Response.json({ initialized })
}

View File

@@ -1,39 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import { URL } from 'url'
import type { PayloadRequest } from '../../types'
import type { Result } from '../operations/login'
import { isNumber } from '../../utilities/isNumber'
import login from '../operations/login'
export default async function loginHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<Response<Result & { message: string }> | void> {
try {
const searchParams = new URL(req.url).searchParams
const depth = searchParams.get('depth')
const result = await login({
collection: req.collection,
// TODO(JARROD): remove reliance on express body parsing
data: req.body,
depth: isNumber(depth) ? depth : undefined,
req,
res,
})
res.status(httpStatus.OK).json({
exp: result.exp,
message: 'Auth Passed',
token: result.token,
user: result.user,
})
} catch (error) {
next(error)
}
}

View File

@@ -1,25 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import logout from '../operations/logout'
export default async function logoutHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<Response<{ message: string }> | void> {
try {
const message = await logout({
collection: req.collection,
req,
res,
})
return res.status(httpStatus.OK).json({ message })
} catch (error) {
return next(error)
}
}

View File

@@ -1,21 +0,0 @@
import type { NextFunction, Response } from 'express'
import type { PayloadRequest } from '../../types'
import me from '../operations/me'
export default async function meHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
const response = await me({
collection: req.collection,
req,
})
return res.status(200).json(response)
} catch (err) {
return next(err)
}
}

View File

@@ -1,36 +0,0 @@
import type { NextFunction, Response } from 'express'
import type { PayloadRequest } from '../../types'
import { extractJWT } from '../getExtractJWT'
import refresh from '../operations/refresh'
export default async function refreshHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
let token
token = extractJWT(req)
if (req.body) {
token = req.body.data
}
const result = await refresh({
collection: req.collection,
req,
res,
token,
})
return res.status(200).json({
message: 'Token refresh successful',
...result,
})
} catch (error) {
return next(error)
}
}

View File

@@ -1,25 +0,0 @@
import type { NextFunction, Response } from 'express'
import type { PayloadRequest } from '../../types'
import registerFirstUser from '../operations/registerFirstUser'
export default async function registerFirstUserHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
const firstUser = await registerFirstUser({
collection: req.collection,
// TODO(JARROD): remove reliance on express body parsing
data: req.body,
req,
res,
})
return res.status(201).json(firstUser)
} catch (error) {
return next(error)
}
}

View File

@@ -1,33 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import resetPassword from '../operations/resetPassword'
async function resetPasswordHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
const result = await resetPassword({
collection: req.collection,
// TODO(JARROD): remove reliance on express body parsing
data: req.body,
req,
res,
})
return res.status(httpStatus.OK).json({
message: 'Password reset successfully.',
token: result.token,
user: result.user,
})
} catch (error) {
return next(error)
}
}
export default resetPasswordHandler

View File

@@ -1,28 +0,0 @@
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import unlock from '../operations/unlock'
export default async function unlockHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
await unlock({
collection: req.collection,
// TODO(JARROD): remove reliance on express body parsing
data: { email: req.body.email },
req,
})
return res.status(httpStatus.OK).json({
message: 'Success',
})
} catch (error) {
return next(error)
}
}

View File

@@ -1,30 +0,0 @@
// TODO(JARROD): remove reliance on express
import type { NextFunction, Response } from 'express'
import httpStatus from 'http-status'
import type { PayloadRequest } from '../../types'
import verifyEmail from '../operations/verifyEmail'
async function verifyEmailHandler(
req: PayloadRequest,
res: Response,
next: NextFunction,
): Promise<any> {
try {
await verifyEmail({
collection: req.collection,
req,
token: req.params.token,
})
return res.status(httpStatus.OK).json({
message: 'Email verified successfully.',
})
} catch (error) {
return next(error)
}
}
export default verifyEmailHandler

View File

@@ -1,13 +0,0 @@
import passport from 'passport'
import AnonymousStrategy from 'passport-anonymous'
import type { Payload } from '../payload'
import jwtStrategy from './strategies/jwt'
function initAuth(ctx: Payload): void {
passport.use(new AnonymousStrategy.Strategy())
passport.use('jwt', jwtStrategy(ctx))
}
export default initAuth

View File

@@ -11,7 +11,7 @@ type Arguments = {
req: PayloadRequest req: PayloadRequest
} }
async function accessOperation(args: Arguments): Promise<Permissions> { export const accessOperation = async (args: Arguments): Promise<Permissions> => {
const { const {
req, req,
req: { payload, user }, req: { payload, user },
@@ -40,5 +40,3 @@ async function accessOperation(args: Arguments): Promise<Permissions> {
throw e throw e
} }
} }
export default accessOperation

View File

@@ -23,7 +23,7 @@ export type Arguments = {
export type Result = string export type Result = string
async function forgotPassword(incomingArgs: Arguments): Promise<null | string> { export const forgotPasswordOperation = async (incomingArgs: Arguments): Promise<null | string> => {
if (!Object.prototype.hasOwnProperty.call(incomingArgs.data, 'email')) { if (!Object.prototype.hasOwnProperty.call(incomingArgs.data, 'email')) {
throw new APIError('Missing email.', 400) throw new APIError('Missing email.', 400)
} }
@@ -66,8 +66,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
// Forget password // Forget password
// ///////////////////////////////////// // /////////////////////////////////////
let token: Buffer | string = crypto.randomBytes(20) let token: string = crypto.randomBytes(20).toString('hex')
token = token.toString('hex')
type UserDoc = { type UserDoc = {
id: number | string id: number | string
@@ -165,5 +164,3 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
throw error throw error
} }
} }
export default forgotPassword

View File

@@ -1,6 +1,9 @@
import type { PayloadRequest } from '../../types' import type { PayloadRequest } from '../../types'
async function init(args: { collection: string; req: PayloadRequest }): Promise<boolean> { export const initOperation = async (args: {
collection: string
req: PayloadRequest
}): Promise<boolean> => {
const { collection: slug, req } = args const { collection: slug, req } = args
const doc = await req.payload.db.findOne({ const doc = await req.payload.db.findOne({
@@ -10,5 +13,3 @@ async function init(args: { collection: string; req: PayloadRequest }): Promise<
return !!doc return !!doc
} }
export default init

View File

@@ -6,7 +6,7 @@ import { getDataLoader } from '../../../collections/dataloader'
import { APIError } from '../../../errors' import { APIError } from '../../../errors'
import { i18nInit } from '../../../translations/init' import { i18nInit } from '../../../translations/init'
import { setRequestContext } from '../../../utilities/setRequestContext' import { setRequestContext } from '../../../utilities/setRequestContext'
import forgotPassword from '../forgotPassword' import { forgotPasswordOperation } from '../forgotPassword'
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -50,7 +50,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
if (!req.t) req.t = req.i18n.t if (!req.t) req.t = req.i18n.t
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req) if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
return forgotPassword({ return forgotPasswordOperation({
collection, collection,
data, data,
disableEmail, disableEmail,

View File

@@ -1,5 +1,3 @@
import type { Response } from 'express'
import type { PayloadT, RequestContext } from '../../..' import type { PayloadT, RequestContext } from '../../..'
import type { GeneratedTypes } from '../../../index' import type { GeneratedTypes } from '../../../index'
import type { PayloadRequest } from '../../../types' import type { PayloadRequest } from '../../../types'
@@ -9,7 +7,7 @@ import { getDataLoader } from '../../../collections/dataloader'
import { APIError } from '../../../errors' import { APIError } from '../../../errors'
import { i18nInit } from '../../../translations/init' import { i18nInit } from '../../../translations/init'
import { setRequestContext } from '../../../utilities/setRequestContext' import { setRequestContext } from '../../../utilities/setRequestContext'
import login from '../login' import { loginOperation } from '../login'
export type Options<TSlug extends keyof GeneratedTypes['collections']> = { export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
collection: TSlug collection: TSlug
@@ -23,7 +21,6 @@ export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
locale?: string locale?: string
overrideAccess?: boolean overrideAccess?: boolean
req?: PayloadRequest req?: PayloadRequest
res?: Response
showHiddenFields?: boolean showHiddenFields?: boolean
} }
@@ -40,7 +37,6 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
locale, locale,
overrideAccess = true, overrideAccess = true,
req = {} as PayloadRequest, req = {} as PayloadRequest,
res,
showHiddenFields, showHiddenFields,
} = options } = options
setRequestContext(req, context) setRequestContext(req, context)
@@ -68,14 +64,13 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
depth, depth,
overrideAccess, overrideAccess,
req, req,
res,
showHiddenFields, showHiddenFields,
} }
if (locale) args.req.locale = locale if (locale) args.req.locale = locale
if (fallbackLocale) args.req.fallbackLocale = fallbackLocale if (fallbackLocale) args.req.fallbackLocale = fallbackLocale
return login<TSlug>(args) return loginOperation<TSlug>(args)
} }
export default localLogin export default localLogin

View File

@@ -7,7 +7,7 @@ import { getDataLoader } from '../../../collections/dataloader'
import { APIError } from '../../../errors' import { APIError } from '../../../errors'
import { i18nInit } from '../../../translations/init' import { i18nInit } from '../../../translations/init'
import { setRequestContext } from '../../../utilities/setRequestContext' import { setRequestContext } from '../../../utilities/setRequestContext'
import resetPassword from '../resetPassword' import { resetPasswordOperation } from '../resetPassword'
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -51,7 +51,7 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
if (!req.t) req.t = req.i18n.t if (!req.t) req.t = req.i18n.t
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req) if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
return resetPassword({ return resetPasswordOperation({
collection, collection,
data, data,
overrideAccess, overrideAccess,

View File

@@ -6,7 +6,7 @@ import { getDataLoader } from '../../../collections/dataloader'
import { APIError } from '../../../errors' import { APIError } from '../../../errors'
import { i18nInit } from '../../../translations/init' import { i18nInit } from '../../../translations/init'
import { setRequestContext } from '../../../utilities/setRequestContext' import { setRequestContext } from '../../../utilities/setRequestContext'
import unlock from '../unlock' import { unlockOperation } from '../unlock'
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -46,7 +46,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
if (!req.t) req.t = req.i18n.t if (!req.t) req.t = req.i18n.t
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req) if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req)
return unlock({ return unlockOperation({
collection, collection,
data, data,
overrideAccess, overrideAccess,

View File

@@ -5,7 +5,7 @@ import type { PayloadRequest } from '../../../types'
import { APIError } from '../../../errors' import { APIError } from '../../../errors'
import { i18nInit } from '../../../translations/init' import { i18nInit } from '../../../translations/init'
import { setRequestContext } from '../../../utilities/setRequestContext' import { setRequestContext } from '../../../utilities/setRequestContext'
import verifyEmail from '../verifyEmail' import { verifyEmailOperation } from '../verifyEmail'
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -33,7 +33,7 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
req.payloadAPI = req.payloadAPI || 'local' req.payloadAPI = req.payloadAPI || 'local'
req.i18n = i18nInit(payload.config.i18n) req.i18n = i18nInit(payload.config.i18n)
return verifyEmail({ return verifyEmailOperation({
collection, collection,
req, req,
token, token,

View File

@@ -9,16 +9,14 @@ import { buildAfterOperation } from '../../collections/operations/utils'
import { AuthenticationError, LockedAuth } from '../../errors' import { AuthenticationError, LockedAuth } from '../../errors'
import { afterRead } from '../../fields/hooks/afterRead' import { afterRead } from '../../fields/hooks/afterRead'
import { commitTransaction } from '../../utilities/commitTransaction' import { commitTransaction } from '../../utilities/commitTransaction'
import { generateCookie } from '../../utilities/cookies'
import getCookieExpiration from '../../utilities/getCookieExpiration'
import { initTransaction } from '../../utilities/initTransaction' import { initTransaction } from '../../utilities/initTransaction'
import { killTransaction } from '../../utilities/killTransaction' import { killTransaction } from '../../utilities/killTransaction'
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields' import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'
import { getFieldsToSign } from '../getFieldsToSign'
import isLocked from '../isLocked' import isLocked from '../isLocked'
import { authenticateLocalStrategy } from '../strategies/local/authenticate' import { authenticateLocalStrategy } from '../strategies/local/authenticate'
import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts' import { incrementLoginAttempts } from '../strategies/local/incrementLoginAttempts'
import { getFieldsToSign } from './getFieldsToSign' import { unlockOperation } from './unlock'
import unlock from './unlock'
export type Result = { export type Result = {
exp?: number exp?: number
@@ -35,15 +33,12 @@ export type Arguments = {
depth?: number depth?: number
overrideAccess?: boolean overrideAccess?: boolean
req: PayloadRequest req: PayloadRequest
responseOptions?: ResponseInit & {
headers: Headers
}
showHiddenFields?: boolean showHiddenFields?: boolean
} }
async function login<TSlug extends keyof GeneratedTypes['collections']>( export const loginOperation = async <TSlug extends keyof GeneratedTypes['collections']>(
incomingArgs: Arguments, incomingArgs: Arguments,
): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> { ): Promise<Result & { user: GeneratedTypes['collections'][TSlug] }> => {
let args = incomingArgs let args = incomingArgs
// ///////////////////////////////////// // /////////////////////////////////////
@@ -70,7 +65,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
req: { req: {
payload, payload,
payload: { config, secret }, payload: { secret },
}, },
showHiddenFields, showHiddenFields,
} = args } = args
@@ -120,7 +115,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
} }
if (maxLoginAttemptsEnabled) { if (maxLoginAttemptsEnabled) {
await unlock({ await unlockOperation({
collection: { collection: {
config: collectionConfig, config: collectionConfig,
}, },
@@ -152,28 +147,6 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
expiresIn: collectionConfig.auth.tokenExpiration, expiresIn: collectionConfig.auth.tokenExpiration,
}) })
if (args.responseOptions) {
const sameSite =
typeof collectionConfig.auth.cookies.sameSite === 'string'
? collectionConfig.auth.cookies.sameSite
: collectionConfig.auth.cookies.sameSite
? 'Strict'
: undefined
const cookie = generateCookie({
name: `${config.cookiePrefix}-token`,
domain: collectionConfig.auth.cookies.domain ?? undefined,
expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
httpOnly: true,
path: '/',
sameSite,
secure: collectionConfig.auth.cookies.secure,
value: token,
})
args.responseOptions.headers.set('Set-Cookie', cookie)
}
req.user = user req.user = user
// ///////////////////////////////////// // /////////////////////////////////////
@@ -273,5 +246,3 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
throw error throw error
} }
} }
export default login

View File

@@ -1,6 +1,3 @@
// TODO(JARROD): remove express Response
import type { Response } from 'express'
import httpStatus from 'http-status' import httpStatus from 'http-status'
import type { Collection } from '../../collections/config/types' import type { Collection } from '../../collections/config/types'
@@ -11,37 +8,20 @@ import { APIError } from '../../errors'
export type Arguments = { export type Arguments = {
collection: Collection collection: Collection
req: PayloadRequest req: PayloadRequest
res: Response
} }
async function logout(incomingArgs: Arguments): Promise<string> { export const logoutOperation = async (incomingArgs: Arguments): Promise<boolean> => {
let args = incomingArgs let args = incomingArgs
const { const {
collection,
collection: { config: collectionConfig }, collection: { config: collectionConfig },
req: { collection, user },
req, req,
req: {
payload: { config },
user,
},
res,
} = incomingArgs } = incomingArgs
if (!user) throw new APIError('No User', httpStatus.BAD_REQUEST) if (!user) throw new APIError('No User', httpStatus.BAD_REQUEST)
if (user.collection !== collectionConfig.slug) if (user.collection !== collectionConfig.slug)
throw new APIError('Incorrect collection', httpStatus.FORBIDDEN) throw new APIError('Incorrect collection', httpStatus.FORBIDDEN)
const cookieOptions = {
domain: undefined,
httpOnly: true,
path: '/',
sameSite: collectionConfig.auth.cookies.sameSite,
secure: collectionConfig.auth.cookies.secure,
}
if (collectionConfig.auth.cookies.domain)
cookieOptions.domain = collectionConfig.auth.cookies.domain
await collection.config.hooks.afterLogout.reduce(async (priorHook, hook) => { await collection.config.hooks.afterLogout.reduce(async (priorHook, hook) => {
await priorHook await priorHook
@@ -50,13 +30,8 @@ async function logout(incomingArgs: Arguments): Promise<string> {
collection: args.collection?.config, collection: args.collection?.config,
context: req.context, context: req.context,
req, req,
res,
})) || args })) || args
}, Promise.resolve()) }, Promise.resolve())
res.clearCookie(`${config.cookiePrefix}-token`, cookieOptions) return true
return req.t('authentication:loggedOutSuccessfully')
} }
export default logout

View File

@@ -5,8 +5,6 @@ import type { Collection } from '../../collections/config/types'
import type { PayloadRequest } from '../../types' import type { PayloadRequest } from '../../types'
import type { User } from '../types' import type { User } from '../types'
import { extractJWT } from '../getExtractJWT'
export type Result = { export type Result = {
collection?: string collection?: string
exp?: number exp?: number
@@ -16,11 +14,16 @@ export type Result = {
export type Arguments = { export type Arguments = {
collection: Collection collection: Collection
currentToken?: string
req: PayloadRequest req: PayloadRequest
} }
async function me({ collection, req }: Arguments): Promise<Result> { export const meOperation = async ({
let response: Result = { collection,
currentToken,
req,
}: Arguments): Promise<Result> => {
let result: Result = {
user: null, user: null,
} }
@@ -45,17 +48,15 @@ async function me({ collection, req }: Arguments): Promise<Result> {
delete user.collection delete user.collection
response = { result = {
collection: req.user.collection, collection: req.user.collection,
user, user,
} }
const token = extractJWT(req) if (currentToken) {
const decoded = jwt.decode(currentToken) as jwt.JwtPayload
if (token) { if (decoded) result.exp = decoded.exp
const decoded = jwt.decode(token) as jwt.JwtPayload if (!collection.config.auth.removeTokenFromResponses) result.token = currentToken
if (decoded) response.exp = decoded.exp
if (!collection.config.auth.removeTokenFromResponses) response.token = token
} }
} }
@@ -66,16 +67,14 @@ async function me({ collection, req }: Arguments): Promise<Result> {
await collection.config.hooks.afterMe.reduce(async (priorHook, hook) => { await collection.config.hooks.afterMe.reduce(async (priorHook, hook) => {
await priorHook await priorHook
response = result =
(await hook({ (await hook({
collection: collection?.config, collection: collection?.config,
context: req.context, context: req.context,
req, req,
response, response: result,
})) || response })) || result
}, Promise.resolve()) }, Promise.resolve())
return response return result
} }
export default me

View File

@@ -1,6 +1,3 @@
// TODO(JARROD): remove express Response
import type { Response } from 'express'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import url from 'url' import url from 'url'
@@ -10,8 +7,7 @@ import type { Document } from '../../types'
import { buildAfterOperation } from '../../collections/operations/utils' import { buildAfterOperation } from '../../collections/operations/utils'
import { Forbidden } from '../../errors' import { Forbidden } from '../../errors'
import getCookieExpiration from '../../utilities/getCookieExpiration' import { getFieldsToSign } from '../getFieldsToSign'
import { getFieldsToSign } from './getFieldsToSign'
export type Result = { export type Result = {
exp: number exp: number
@@ -22,11 +18,10 @@ export type Result = {
export type Arguments = { export type Arguments = {
collection: Collection collection: Collection
req: PayloadRequest req: PayloadRequest
res?: Response
token: string token: string
} }
async function refresh(incomingArgs: Arguments): Promise<Result> { export const refreshOperation = async (incomingArgs: Arguments): Promise<Result> => {
let args = incomingArgs let args = incomingArgs
// ///////////////////////////////////// // /////////////////////////////////////
@@ -83,22 +78,6 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
const exp = (jwt.decode(refreshedToken) as Record<string, unknown>).exp as number const exp = (jwt.decode(refreshedToken) as Record<string, unknown>).exp as number
if (args.res) {
const cookieOptions = {
domain: undefined,
expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
httpOnly: true,
path: '/',
sameSite: collectionConfig.auth.cookies.sameSite,
secure: collectionConfig.auth.cookies.secure,
}
if (collectionConfig.auth.cookies.domain)
cookieOptions.domain = collectionConfig.auth.cookies.domain
args.res.cookie(`${config.cookiePrefix}-token`, refreshedToken, cookieOptions)
}
let result: Result = { let result: Result = {
exp, exp,
refreshedToken, refreshedToken,
@@ -118,7 +97,6 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
context: args.req.context, context: args.req.context,
exp, exp,
req: args.req, req: args.req,
res: args.res,
token: refreshedToken, token: refreshedToken,
})) || result })) || result
}, Promise.resolve()) }, Promise.resolve())
@@ -144,5 +122,3 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
return result return result
} }
export default refresh

View File

@@ -1,5 +1,3 @@
// TODO(JARROD): remove express Response
import type { Response } from 'express'
import type { MarkOptional } from 'ts-essentials' import type { MarkOptional } from 'ts-essentials'
import type { GeneratedTypes } from '../../' import type { GeneratedTypes } from '../../'
@@ -18,18 +16,17 @@ export type Arguments<T extends { [field: number | string | symbol]: unknown }>
password: string password: string
} }
req: PayloadRequest req: PayloadRequest
// TODO(JARROD): remove express Response
res: Response
} }
export type Result<T> = { export type Result<T> = {
message: string exp?: number
user: T token?: string
user?: T
} }
async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections']>( export const registerFirstUserOperation = async <TSlug extends keyof GeneratedTypes['collections']>(
args: Arguments<GeneratedTypes['collections'][TSlug]>, args: Arguments<GeneratedTypes['collections'][TSlug]>,
): Promise<Result<GeneratedTypes['collections'][TSlug]>> { ): Promise<Result<GeneratedTypes['collections'][TSlug]>> => {
const { const {
collection: { collection: {
config, config,
@@ -80,27 +77,21 @@ async function registerFirstUser<TSlug extends keyof GeneratedTypes['collections
// Log in new user // Log in new user
// ///////////////////////////////////// // /////////////////////////////////////
const { token } = await payload.login({ const { exp, token } = await payload.login({
...args, ...args,
collection: slug, collection: slug,
req, req,
}) })
const resultToReturn = {
...result,
token,
}
if (shouldCommit) await commitTransaction(req) if (shouldCommit) await commitTransaction(req)
return { return {
message: 'Registered and logged in successfully. Welcome!', exp,
user: resultToReturn, token,
user: result,
} }
} catch (error: unknown) { } catch (error: unknown) {
await killTransaction(req) await killTransaction(req)
throw error throw error
} }
} }
export default registerFirstUser

View File

@@ -1,6 +1,3 @@
// TODO(JARROD): remove express Response
import type { Response } from 'express'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import type { Collection } from '../../collections/config/types' import type { Collection } from '../../collections/config/types'
@@ -8,12 +5,11 @@ import type { PayloadRequest } from '../../types'
import { APIError } from '../../errors' import { APIError } from '../../errors'
import { commitTransaction } from '../../utilities/commitTransaction' import { commitTransaction } from '../../utilities/commitTransaction'
import getCookieExpiration from '../../utilities/getCookieExpiration'
import { initTransaction } from '../../utilities/initTransaction' import { initTransaction } from '../../utilities/initTransaction'
import { killTransaction } from '../../utilities/killTransaction' import { killTransaction } from '../../utilities/killTransaction'
import { getFieldsToSign } from '../getFieldsToSign'
import { authenticateLocalStrategy } from '../strategies/local/authenticate' import { authenticateLocalStrategy } from '../strategies/local/authenticate'
import { generatePasswordSaltHash } from '../strategies/local/generatePasswordSaltHash' import { generatePasswordSaltHash } from '../strategies/local/generatePasswordSaltHash'
import { getFieldsToSign } from './getFieldsToSign'
export type Result = { export type Result = {
token?: string token?: string
@@ -29,11 +25,9 @@ export type Arguments = {
depth?: number depth?: number
overrideAccess?: boolean overrideAccess?: boolean
req: PayloadRequest req: PayloadRequest
// TODO(JARROD): remove express Response
res?: Response
} }
async function resetPassword(args: Arguments): Promise<Result> { export const resetPasswordOperation = async (args: Arguments): Promise<Result> => {
if ( if (
!Object.prototype.hasOwnProperty.call(args.data, 'token') || !Object.prototype.hasOwnProperty.call(args.data, 'token') ||
!Object.prototype.hasOwnProperty.call(args.data, 'password') !Object.prototype.hasOwnProperty.call(args.data, 'password')
@@ -47,7 +41,7 @@ async function resetPassword(args: Arguments): Promise<Result> {
depth, depth,
overrideAccess, overrideAccess,
req: { req: {
payload: { config, secret }, payload: { secret },
payload, payload,
}, },
req, req,
@@ -102,22 +96,6 @@ async function resetPassword(args: Arguments): Promise<Result> {
expiresIn: collectionConfig.auth.tokenExpiration, expiresIn: collectionConfig.auth.tokenExpiration,
}) })
if (args.res) {
const cookieOptions = {
domain: undefined,
expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
httpOnly: true,
path: '/',
sameSite: collectionConfig.auth.cookies.sameSite,
secure: collectionConfig.auth.cookies.secure,
}
if (collectionConfig.auth.cookies.domain)
cookieOptions.domain = collectionConfig.auth.cookies.domain
args.res.cookie(`${config.cookiePrefix}-token`, token, cookieOptions)
}
const fullUser = await payload.findByID({ const fullUser = await payload.findByID({
id: user.id, id: user.id,
collection: collectionConfig.slug, collection: collectionConfig.slug,
@@ -137,4 +115,4 @@ async function resetPassword(args: Arguments): Promise<Result> {
} }
} }
export default resetPassword export default resetPasswordOperation

View File

@@ -17,7 +17,7 @@ export type Args = {
req: PayloadRequest req: PayloadRequest
} }
async function unlock(args: Args): Promise<boolean> { export const unlockOperation = async (args: Args): Promise<boolean> => {
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) { if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.') throw new APIError('Missing email.')
} }
@@ -25,7 +25,7 @@ async function unlock(args: Args): Promise<boolean> {
const { const {
collection: { config: collectionConfig }, collection: { config: collectionConfig },
overrideAccess, overrideAccess,
req: { locale, payload }, req: { locale },
req, req,
} = args } = args
@@ -82,4 +82,4 @@ async function unlock(args: Args): Promise<boolean> {
} }
} }
export default unlock export default unlockOperation

View File

@@ -14,7 +14,7 @@ export type Args = {
token: string token: string
} }
async function verifyEmail(args: Args): Promise<boolean> { export const verifyEmailOperation = async (args: Args): Promise<boolean> => {
const { collection, req, token } = args const { collection, req, token } = args
if (!Object.prototype.hasOwnProperty.call(args, 'token')) { if (!Object.prototype.hasOwnProperty.call(args, 'token')) {
throw new APIError('Missing required data.', httpStatus.BAD_REQUEST) throw new APIError('Missing required data.', httpStatus.BAD_REQUEST)
@@ -55,4 +55,4 @@ async function verifyEmail(args: Args): Promise<boolean> {
} }
} }
export default verifyEmail export default verifyEmailOperation

View File

@@ -1,12 +0,0 @@
import type { NextFunction, Request, Response } from 'express'
import type { Collection } from './config/types'
const bindCollectionMiddleware =
(collection: Collection) =>
(req: Request & { collection: Collection }, res: Response, next: NextFunction): void => {
req.collection = collection
next()
}
export default bindCollectionMiddleware

View File

@@ -1,177 +0,0 @@
import type { Endpoint } from '../config/types'
import type { SanitizedCollectionConfig } from './config/types'
import forgotPasswordHandler from '../auth/handlers/forgotPassword'
import initHandler from '../auth/handlers/init'
import loginHandler from '../auth/handlers/login'
import logoutHandler from '../auth/handlers/logout'
import meHandler from '../auth/handlers/me'
import refreshHandler from '../auth/handlers/refresh'
import registerFirstUserHandler from '../auth/handlers/registerFirstUser'
import resetPassword from '../auth/handlers/resetPassword'
import unlock from '../auth/handlers/unlock'
import verifyEmail from '../auth/handlers/verifyEmail'
import create from './requestHandlers/create'
import deleteHandler from './requestHandlers/delete'
import deleteByID from './requestHandlers/deleteByID'
import docAccessRequestHandler from './requestHandlers/docAccess'
import find from './requestHandlers/find'
import findByID from './requestHandlers/findByID'
import findVersionByID from './requestHandlers/findVersionByID'
import findVersions from './requestHandlers/findVersions'
import restoreVersion from './requestHandlers/restoreVersion'
import update from './requestHandlers/update'
import updateByID, { deprecatedUpdate } from './requestHandlers/updateByID'
const buildEndpoints = (collection: SanitizedCollectionConfig): Endpoint[] => {
if (!collection.endpoints) return []
const endpoints = [...collection.endpoints]
if (collection.auth) {
if (!collection.auth.disableLocalStrategy) {
if (collection.auth.verify) {
endpoints.push({
handler: verifyEmail,
method: 'post',
path: '/verify/:token',
})
}
if (collection.auth.maxLoginAttempts > 0) {
endpoints.push({
handler: unlock,
method: 'post',
path: '/unlock',
})
}
endpoints.push(
{
handler: loginHandler,
method: 'post',
path: '/login',
},
{
handler: registerFirstUserHandler,
method: 'post',
path: '/first-register',
},
{
handler: forgotPasswordHandler,
method: 'post',
path: '/forgot-password',
},
{
handler: resetPassword,
method: 'post',
path: '/reset-password',
},
)
}
endpoints.push(
{
handler: initHandler,
method: 'get',
path: '/init',
},
{
handler: meHandler,
method: 'get',
path: '/me',
},
{
handler: logoutHandler,
method: 'post',
path: '/logout',
},
{
handler: refreshHandler,
method: 'post',
path: '/refresh-token',
},
)
}
if (collection.versions) {
endpoints.push(
{
handler: findVersions,
method: 'get',
path: '/versions',
},
{
handler: findVersionByID,
method: 'get',
path: '/versions/:id',
},
{
handler: restoreVersion,
method: 'post',
path: '/versions/:id',
},
)
}
endpoints.push(
{
handler: find,
method: 'get',
path: '/',
},
{
handler: create,
method: 'post',
path: '/',
},
{
handler: docAccessRequestHandler,
method: 'get',
path: '/access/:id',
},
{
handler: docAccessRequestHandler,
method: 'post',
path: '/access/:id',
},
{
handler: docAccessRequestHandler,
method: 'post',
path: '/access',
},
{
handler: deprecatedUpdate,
method: 'put',
path: '/:id',
},
{
handler: update,
method: 'patch',
path: '/',
},
{
handler: updateByID,
method: 'patch',
path: '/:id',
},
{
handler: findByID,
method: 'get',
path: '/:id',
},
{
handler: deleteByID,
method: 'delete',
path: '/:id',
},
{
handler: deleteHandler,
method: 'delete',
path: '/',
},
)
return endpoints
}
export default buildEndpoints

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Response } from 'express'
import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql' import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql'
import type { DeepRequired } from 'ts-essentials' import type { DeepRequired } from 'ts-essentials'
@@ -169,7 +167,6 @@ export type AfterLogoutHook<T extends TypeWithID = any> = (args: {
collection: SanitizedCollectionConfig collection: SanitizedCollectionConfig
context: RequestContext context: RequestContext
req: PayloadRequest req: PayloadRequest
res: Response
}) => any }) => any
export type AfterMeHook<T extends TypeWithID = any> = (args: { export type AfterMeHook<T extends TypeWithID = any> = (args: {
@@ -186,7 +183,6 @@ export type AfterRefreshHook<T extends TypeWithID = any> = (args: {
context: RequestContext context: RequestContext
exp: number exp: number
req: PayloadRequest req: PayloadRequest
res: Response
token: string token: string
}) => any }) => any

View File

@@ -7,9 +7,9 @@ import {
GraphQLString, GraphQLString,
} from 'graphql' } from 'graphql'
import type { PayloadT } from '../..'
import type { Field } from '../../fields/config/types' import type { Field } from '../../fields/config/types'
import type { ObjectTypeConfig } from '../../graphql/schema/buildObjectType' import type { ObjectTypeConfig } from '../../graphql/schema/buildObjectType'
import type { Payload } from '../../payload'
import type { Collection, SanitizedCollectionConfig } from '../config/types' import type { Collection, SanitizedCollectionConfig } from '../config/types'
import forgotPassword from '../../auth/graphql/resolvers/forgotPassword' import forgotPassword from '../../auth/graphql/resolvers/forgotPassword'
@@ -42,7 +42,7 @@ import findVersionsResolver from './resolvers/findVersions'
import restoreVersionResolver from './resolvers/restoreVersion' import restoreVersionResolver from './resolvers/restoreVersion'
import updateResolver from './resolvers/update' import updateResolver from './resolvers/update'
function initCollectionsGraphQL(payload: Payload): void { function initCollectionsGraphQL(payload: PayloadT): void {
Object.keys(payload.collections).forEach((slug) => { Object.keys(payload.collections).forEach((slug) => {
const collection: Collection = payload.collections[slug] const collection: Collection = payload.collections[slug]
const { const {

View File

@@ -7,7 +7,7 @@ import type { PayloadRequest } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import create from '../../operations/create' import { createOperation } from '../../operations/create'
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = ( export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
_: unknown, _: unknown,
@@ -21,7 +21,6 @@ export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
}, },
context: { context: {
req: PayloadRequest req: PayloadRequest
res: Response
}, },
) => Promise<GeneratedTypes['collections'][TSlug]> ) => Promise<GeneratedTypes['collections'][TSlug]>
@@ -41,7 +40,7 @@ export default function createResolver<TSlug extends keyof GeneratedTypes['colle
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await create(options) const result = await createOperation(options)
return result return result
} }

View File

@@ -6,7 +6,7 @@ import type { PayloadRequest } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import deleteByID from '../../operations/deleteByID' import { deleteByIDOperation } from '../../operations/deleteByID'
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = ( export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
_: unknown, _: unknown,
@@ -34,7 +34,7 @@ export default function getDeleteResolver<TSlug extends keyof GeneratedTypes['co
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await deleteByID(options) const result = await deleteByIDOperation(options)
return result return result
} }

View File

@@ -2,7 +2,7 @@ import type { CollectionPermission, GlobalPermission } from '../../../auth'
import type { PayloadRequest } from '../../../types' import type { PayloadRequest } from '../../../types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import { docAccess } from '../../operations/docAccess' import { docAccessOperation } from '../../operations/docAccess'
export type Resolver = ( export type Resolver = (
_: unknown, _: unknown,
@@ -17,7 +17,7 @@ export type Resolver = (
export function docAccessResolver(): Resolver { export function docAccessResolver(): Resolver {
async function resolver(_, args, context) { async function resolver(_, args, context) {
return docAccess({ return docAccessOperation({
id: args.id, id: args.id,
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
}) })

View File

@@ -5,7 +5,7 @@ import type { Where } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import find from '../../operations/find' import { findOperation } from '../../operations/find'
export type Resolver = ( export type Resolver = (
_: unknown, _: unknown,
@@ -42,7 +42,7 @@ export default function findResolver(collection: Collection): Resolver {
where: args.where, where: args.where,
} }
const results = await find(options) const results = await findOperation(options)
return results return results
} }
} }

View File

@@ -3,7 +3,7 @@ import type { PayloadRequest } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import findByID from '../../operations/findByID' import { findByIDOperation } from '../../operations/findByID'
export type Resolver<T> = ( export type Resolver<T> = (
_: unknown, _: unknown,
@@ -35,7 +35,7 @@ export default function findByIDResolver<T extends keyof GeneratedTypes['collect
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await findByID(options) const result = await findByIDOperation(options)
return result return result
} }

View File

@@ -6,7 +6,7 @@ import type { TypeWithVersion } from '../../../versions/types'
import type { Collection, TypeWithID } from '../../config/types' import type { Collection, TypeWithID } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import findVersionByID from '../../operations/findVersionByID' import { findVersionByIDOperation } from '../../operations/findVersionByID'
export type Resolver<T extends TypeWithID = any> = ( export type Resolver<T extends TypeWithID = any> = (
_: unknown, _: unknown,
@@ -35,7 +35,7 @@ export default function findVersionByIDResolver(collection: Collection): Resolve
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await findVersionByID(options) const result = await findVersionByIDOperation(options)
return result return result
} }

View File

@@ -8,7 +8,7 @@ import type { Where } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import findVersions from '../../operations/findVersions' import { findVersionsOperation } from '../../operations/findVersions'
export type Resolver = ( export type Resolver = (
_: unknown, _: unknown,
@@ -41,7 +41,7 @@ export default function findVersionsResolver(collection: Collection): Resolver {
where: args.where, where: args.where,
} }
const result = await findVersions(options) const result = await findVersionsOperation(options)
return result return result
} }

View File

@@ -5,7 +5,7 @@ import type { PayloadRequest } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import restoreVersion from '../../operations/restoreVersion' import { restoreVersionOperation } from '../../operations/restoreVersion'
export type Resolver = ( export type Resolver = (
_: unknown, _: unknown,
@@ -27,7 +27,7 @@ export default function restoreVersionResolver(collection: Collection): Resolver
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await restoreVersion(options) const result = await restoreVersionOperation(options)
return result return result
} }

View File

@@ -6,7 +6,7 @@ import type { PayloadRequest } from '../../../types'
import type { Collection } from '../../config/types' import type { Collection } from '../../config/types'
import isolateTransactionID from '../../../utilities/isolateTransactionID' import isolateTransactionID from '../../../utilities/isolateTransactionID'
import updateByID from '../../operations/updateByID' import { updateByIDOperation } from '../../operations/updateByID'
export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = ( export type Resolver<TSlug extends keyof GeneratedTypes['collections']> = (
_: unknown, _: unknown,
@@ -40,7 +40,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['colle
req: isolateTransactionID(context.req), req: isolateTransactionID(context.req),
} }
const result = await updateByID<TSlug>(options) const result = await updateByIDOperation<TSlug>(options)
return result return result
} }

View File

@@ -1,43 +0,0 @@
import express from 'express'
import passport from 'passport'
import type { Payload } from '../payload'
import type { SanitizedCollectionConfig } from './config/types'
import apiKeyStrategy from '../auth/strategies/apiKey'
import mountEndpoints from '../express/mountEndpoints'
import bindCollectionMiddleware from './bindCollection'
import buildEndpoints from './buildEndpoints'
export default function initCollectionsHTTP(ctx: Payload): void {
ctx.config.collections = ctx.config.collections.map((collection: SanitizedCollectionConfig) => {
const formattedCollection = collection
const router = express.Router()
const { slug } = collection
router.all('*', bindCollectionMiddleware(ctx.collections[formattedCollection.slug]))
if (collection.auth) {
const { config } = ctx.collections[formattedCollection.slug]
if (collection.auth.useAPIKey) {
passport.use(`${config.slug}-api-key`, apiKeyStrategy(ctx, config))
}
if (Array.isArray(collection.auth.strategies)) {
collection.auth.strategies.forEach(({ name, strategy }, index) => {
const passportStrategy = typeof strategy === 'object' ? strategy : strategy(ctx)
passport.use(`${config.slug}-${name ?? index}`, passportStrategy)
})
}
}
const endpoints = buildEndpoints(collection)
mountEndpoints(ctx.express, router, endpoints)
ctx.router.use(`/${slug}`, router)
return formattedCollection
})
}

Some files were not shown because too many files have changed in this diff Show More