diff --git a/src/auth/types.ts b/src/auth/types.ts index 89554de8fa..e767b6f60a 100644 --- a/src/auth/types.ts +++ b/src/auth/types.ts @@ -1,3 +1,4 @@ +import { DeepRequired } from 'ts-essentials'; import { PayloadRequest } from '../express/types/payloadRequest'; export type Permission = { @@ -67,30 +68,28 @@ export interface IncomingAuthType { verify?: | boolean | { - generateEmailHTML: GenerateVerifyEmailHTML; - generateEmailSubject: GenerateVerifyEmailSubject; + generateEmailHTML?: GenerateVerifyEmailHTML; + generateEmailSubject?: GenerateVerifyEmailSubject; }; maxLoginAttempts?: number; lockTime?: number; useAPIKey?: boolean; - cookies?: - | { + cookies?: { secure?: boolean; sameSite?: string; domain?: string; } - | boolean; forgotPassword?: { generateEmailHTML?: GenerateForgotPasswordEmailHTML, generateEmailSubject?: GenerateForgotPasswordEmailSubject, } } -export interface Auth extends Omit { +export interface Auth extends Omit, 'verify' | 'forgotPassword'> { verify?: { generateEmailHTML?: GenerateVerifyEmailHTML generateEmailSubject?: GenerateVerifyEmailSubject - } + } | boolean forgotPassword?: { generateEmailHTML?: GenerateForgotPasswordEmailHTML generateEmailSubject?: GenerateForgotPasswordEmailSubject diff --git a/src/collections/config/sanitize.ts b/src/collections/config/sanitize.ts index c039774d58..2463a41da1 100644 --- a/src/collections/config/sanitize.ts +++ b/src/collections/config/sanitize.ts @@ -1,5 +1,5 @@ import merge from 'deepmerge'; -import { PayloadCollectionConfig, CollectionConfig } from './types'; +import { CollectionConfig, PayloadCollectionConfig } from './types'; import sanitizeFields from '../../fields/config/sanitize'; import toKebabCase from '../../utilities/toKebabCase'; import baseAuthFields from '../../fields/baseFields/baseFields'; @@ -58,20 +58,30 @@ const sanitizeCollection = (collections: PayloadCollectionConfig[], collection: // Make copy of collection config // ///////////////////////////////// - const sanitized: PayloadCollectionConfig = { ...collection }; + const sanitized = { ...collection }; sanitized.slug = toKebabCase(sanitized.slug); sanitized.labels = !sanitized.labels ? formatLabels(sanitized.slug) : sanitized.labels; // ///////////////////////////////// - // Add required base fields + // Ensure that collection has required object structure // ///////////////////////////////// - if (collection.upload) { - if (typeof collection.upload === 'object') { - sanitized.upload = collection.upload; - } else { - sanitized.upload = {}; - } + if (!sanitized.hooks) sanitized.hooks = {}; + if (!sanitized.access) sanitized.access = {}; + if (!sanitized.admin) sanitized.admin = {}; + + if (!sanitized.hooks.beforeOperation) sanitized.hooks.beforeOperation = []; + if (!sanitized.hooks.beforeValidate) sanitized.hooks.beforeValidate = []; + if (!sanitized.hooks.beforeChange) sanitized.hooks.beforeChange = []; + if (!sanitized.hooks.afterChange) sanitized.hooks.afterChange = []; + if (!sanitized.hooks.beforeRead) sanitized.hooks.beforeRead = []; + if (!sanitized.hooks.afterRead) sanitized.hooks.afterRead = []; + if (!sanitized.hooks.beforeDelete) sanitized.hooks.beforeDelete = []; + if (!sanitized.hooks.afterDelete) sanitized.hooks.afterDelete = []; + + + if (sanitized.upload) { + if (sanitized.upload === true) sanitized.upload = {}; if (!sanitized.upload.staticDir) sanitized.upload.staticDir = sanitized.slug; if (!sanitized.upload.staticURL) sanitized.upload.staticURL = `/${sanitized.slug}`; @@ -91,13 +101,18 @@ const sanitizeCollection = (collections: PayloadCollectionConfig[], collection: ]; } - if (collection.auth) { + if (sanitized.auth) { if (typeof collection.auth === 'object') { sanitized.auth = collection.auth; } else { sanitized.auth = {}; } + if (!sanitized.hooks.beforeLogin) sanitized.hooks.beforeLogin = []; + if (!sanitized.hooks.afterLogin) sanitized.hooks.afterLogin = []; + if (!sanitized.hooks.afterForgotPassword) sanitized.hooks.afterForgotPassword = []; + if (!sanitized.auth.forgotPassword) sanitized.auth.forgotPassword = {}; + let authFields = baseAuthFields; if (sanitized.auth.useAPIKey) { @@ -105,13 +120,10 @@ const sanitizeCollection = (collections: PayloadCollectionConfig[], collection: } if (sanitized.auth.verify) { + if (sanitized.auth.verify === true) sanitized.auth.verify = {}; authFields = authFields.concat(baseVerificationFields); } - if (!sanitized?.hooks?.beforeLogin) sanitized.hooks.beforeLogin = []; - if (!sanitized?.hooks?.afterLogin) sanitized.hooks.afterLogin = []; - if (!sanitized?.hooks?.afterForgotPassword) sanitized.hooks.afterForgotPassword = []; - sanitized.auth.maxLoginAttempts = typeof sanitized.auth.maxLoginAttempts === 'undefined' ? 5 : sanitized.auth.maxLoginAttempts; sanitized.auth.lockTime = sanitized.auth.lockTime || 600000; // 10 minutes @@ -129,7 +141,6 @@ const sanitizeCollection = (collections: PayloadCollectionConfig[], collection: if (!sanitized.auth.cookies.secure) sanitized.auth.cookies.secure = false; if (!sanitized.auth.cookies.sameSite) sanitized.auth.cookies.sameSite = 'Lax'; } - authFields = mergeBaseFields(sanitized.fields, authFields); sanitized.fields = [ @@ -145,7 +156,7 @@ const sanitizeCollection = (collections: PayloadCollectionConfig[], collection: const validRelationships = collections.map((c) => c.slug); sanitized.fields = sanitizeFields(sanitized.fields, validRelationships); - return sanitized; + return sanitized as CollectionConfig; }; export default sanitizeCollection; diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index 1c789314ce..0244ad97ee 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -4,7 +4,8 @@ import { PaginateModel, Document, PassportLocalModel } from 'mongoose'; import { Access } from '../../config/types'; import { Field } from '../../fields/config/types'; import { PayloadRequest } from '../../express/types/payloadRequest'; -import { Auth } from '../../auth/types'; +import { IncomingAuthType, Auth } from '../../auth/types'; +import { IncomingUploadType, Upload } from '../../uploads/types'; interface CollectionModel extends PaginateModel, PassportLocalModel{} @@ -79,20 +80,6 @@ export type AfterForgotPasswordHook = (args?: { args?: any; }) => any; -export type ImageSize = { - name: string, - width: number, - height: number, - crop: string, // comes from sharp package -}; - -export type Upload = { - imageSizes?: ImageSize[]; - staticURL?: string; - staticDir?: string; - adminThumbnail?: string; -} - export type PayloadCollectionConfig = { slug: string; labels?: { @@ -126,13 +113,13 @@ export type PayloadCollectionConfig = { admin?: Access; unlock?: Access; }; - auth?: Auth | boolean; - upload?: Upload | boolean; + auth?: IncomingAuthType | boolean; + upload?: IncomingUploadType | boolean; }; -export interface CollectionConfig extends DeepRequired { - auth: DeepRequired - upload: DeepRequired +export interface CollectionConfig extends Omit, 'auth' | 'upload'> { + auth: Auth | boolean + upload: Upload | boolean } export type Collection = { diff --git a/src/config/build.ts b/src/config/build.ts index 1e410bf8ff..38734f6131 100644 --- a/src/config/build.ts +++ b/src/config/build.ts @@ -1,6 +1,5 @@ import { PayloadConfig, Config } from './types'; import sanitize from './sanitize'; -import validate from './validate'; /** * @description Builds and validates Payload configuration @@ -8,8 +7,7 @@ import validate from './validate'; * @returns Built and sanitized Payload Config */ export function buildConfig(config: PayloadConfig): Config { - const validated = validate(config); - const sanitized = sanitize(validated); + const sanitized = sanitize(config); return sanitized; } diff --git a/src/config/load.ts b/src/config/load.ts index fa16f6e0d1..83cd8aadb4 100644 --- a/src/config/load.ts +++ b/src/config/load.ts @@ -3,6 +3,7 @@ import path from 'path'; import { Config } from './types'; import findConfig from './find'; +import validate from './validate'; const removedExtensions = ['.scss', '.css', '.svg', '.png', '.jpg', '.eot', '.ttf', '.woff', '.woff2']; @@ -14,14 +15,16 @@ const loadConfig = (): Config => { }); // eslint-disable-next-line @typescript-eslint/no-var-requires - let publicConfig = require(configPath); + let config = require(configPath); - if (publicConfig.default) publicConfig = publicConfig.default; + if (config.default) config = config.default; + + const validatedConfig = validate(config); return { - ...publicConfig, + ...validatedConfig, paths: { - ...(publicConfig.paths || {}), + ...(validatedConfig.paths || {}), configDir: path.dirname(configPath), config: configPath, }, diff --git a/src/config/sanitize.ts b/src/config/sanitize.ts index 815b241b2e..14cb827fd9 100644 --- a/src/config/sanitize.ts +++ b/src/config/sanitize.ts @@ -1,14 +1,16 @@ -import { Config } from './types'; +import { PayloadConfig, Config } from './types'; import defaultUser from '../auth/defaultUser'; import sanitizeCollection from '../collections/config/sanitize'; import { InvalidConfiguration } from '../errors'; import sanitizeGlobals from '../globals/config/sanitize'; import checkDuplicateCollections from '../utilities/checkDuplicateCollections'; -const sanitizeConfig = (config: Config): Config => { +const sanitizeConfig = (config: PayloadConfig): Config => { const sanitizedConfig = { ...config }; - sanitizedConfig.csrf.push(config.serverURL); + if (sanitizedConfig.publicENV === undefined) sanitizedConfig.publicENV = {}; + + if (sanitizedConfig.defaultDepth === undefined) sanitizedConfig.defaultDepth = 2; sanitizedConfig.collections = sanitizedConfig.collections.map((collection) => sanitizeCollection(sanitizedConfig.collections, collection)); checkDuplicateCollections(sanitizedConfig.collections); @@ -19,6 +21,21 @@ const sanitizeConfig = (config: Config): Config => { sanitizedConfig.globals = []; } + if (!sanitizedConfig.cookiePrefix) sanitizedConfig.cookiePrefix = 'payload'; + + sanitizedConfig.csrf = [ + ...(Array.isArray(config.csrf) ? config.csrf : []), + config.serverURL, + ]; + + sanitizedConfig.admin = config.admin || {}; + + if (!sanitizedConfig.admin.meta) { + sanitizedConfig.admin.meta = {}; + } + + sanitizedConfig.upload = config.upload || {}; + if (!sanitizedConfig.admin.user) { sanitizedConfig.admin.user = 'users'; sanitizedConfig.collections.push(sanitizeCollection(sanitizedConfig.collections, defaultUser)); @@ -26,7 +43,33 @@ const sanitizeConfig = (config: Config): Config => { throw new InvalidConfiguration(`${sanitizedConfig.admin.user} is not a valid admin user collection`); } - return sanitizedConfig; + sanitizedConfig.email = config.email || {}; + // if (!sanitizedConfig.email.transport) sanitizedConfig.email.transport = 'mock'; + + sanitizedConfig.graphQL = config.graphQL || {}; + sanitizedConfig.graphQL.maxComplexity = (sanitizedConfig.graphQL && sanitizedConfig.graphQL.maxComplexity) ? sanitizedConfig.graphQL.maxComplexity : 1000; + sanitizedConfig.graphQL.disablePlaygroundInProduction = (sanitizedConfig.graphQL && sanitizedConfig.graphQL.disablePlaygroundInProduction !== undefined) ? sanitizedConfig.graphQL.disablePlaygroundInProduction : true; + + sanitizedConfig.routes = { + admin: (config.routes && config.routes.admin) ? config.routes.admin : '/admin', + api: (config.routes && config.routes.api) ? config.routes.api : '/api', + graphQL: (config.routes && config.routes.graphQL) ? config.routes.graphQL : '/graphql', + graphQLPlayground: (config.routes && config.routes.graphQLPlayground) ? config.routes.graphQLPlayground : '/graphql-playground', + }; + + sanitizedConfig.rateLimit = config.rateLimit || {}; + sanitizedConfig.rateLimit.window = sanitizedConfig?.rateLimit?.window || 15 * 60 * 100; // 15min default + sanitizedConfig.rateLimit.max = sanitizedConfig?.rateLimit?.max || 500; + + if (!sanitizedConfig.express) { + sanitizedConfig.express = { + json: {}, + }; + } + + sanitizedConfig.hooks = { ...(config.hooks || {}) }; + + return sanitizedConfig as Config; }; export default sanitizeConfig; diff --git a/src/index.ts b/src/index.ts index cf5a425705..a7d7613468 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import express, { Express, Router } from 'express'; import crypto from 'crypto'; import { TestAccount } from 'nodemailer'; -import { AuthenticateOptions } from 'passport'; import { Config, EmailOptions, diff --git a/src/types/index.ts b/src/types/index.ts index ebae56acae..85cc4d1637 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,10 +8,7 @@ export type Where = { [key: string]: unknown } -export type Document = { - id: string; - [key: string]: unknown; -}; +export type Document = any; export type CreateOptions = { collection: string; diff --git a/src/uploads/types.ts b/src/uploads/types.ts index bd487e8e85..3c0c706d32 100644 --- a/src/uploads/types.ts +++ b/src/uploads/types.ts @@ -18,3 +18,24 @@ export type FileData = { height: number; sizes: FileSizes; }; + +export type ImageSize = { + name: string, + width: number, + height: number, + crop: string, // comes from sharp package +}; + +export type IncomingUploadType = { + imageSizes?: ImageSize[]; + staticURL?: string; + staticDir?: string; + adminThumbnail?: string; +} + +export type Upload = { + imageSizes?: ImageSize[] + staticURL: string + staticDir: string + adminThumbnail?: string +} diff --git a/src/utilities/canUseDOM.ts b/src/utilities/canUseDOM.ts new file mode 100644 index 0000000000..472188d327 --- /dev/null +++ b/src/utilities/canUseDOM.ts @@ -0,0 +1,4 @@ +export default !!( + (typeof window !== 'undefined' + && window.document && window.document.createElement) +); diff --git a/src/utilities/checkDuplicateCollections.ts b/src/utilities/checkDuplicateCollections.ts index f3d0f23870..366df08d96 100644 --- a/src/utilities/checkDuplicateCollections.ts +++ b/src/utilities/checkDuplicateCollections.ts @@ -1,9 +1,9 @@ import { DuplicateCollection } from '../errors'; -import { CollectionConfig } from '../collections/config/types'; +import { PayloadCollectionConfig } from '../collections/config/types'; const getDuplicates = (arr: string[]) => arr.filter((item, index) => arr.indexOf(item) !== index); -const checkDuplicateCollections = (collections: CollectionConfig[]): void => { +const checkDuplicateCollections = (collections: PayloadCollectionConfig[]): void => { const duplicateSlugs = getDuplicates(collections.map((c) => c.slug)); if (duplicateSlugs.length > 0) { throw new DuplicateCollection('slug', duplicateSlugs); diff --git a/src/webpack/mocks/emptyModule.js b/src/webpack/mocks/emptyModule.js index ff8b4c5632..2d1ec23827 100644 --- a/src/webpack/mocks/emptyModule.js +++ b/src/webpack/mocks/emptyModule.js @@ -1 +1 @@ -export default {}; +export default () => {};