replaces sanitization

This commit is contained in:
James
2020-11-29 20:08:06 -05:00
parent 6f1d61aabc
commit c3c3f3fc88
12 changed files with 124 additions and 62 deletions

View File

@@ -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<IncomingAuthType, 'verify' | 'forgotPassword'> {
export interface Auth extends Omit<DeepRequired<IncomingAuthType>, 'verify' | 'forgotPassword'> {
verify?: {
generateEmailHTML?: GenerateVerifyEmailHTML
generateEmailSubject?: GenerateVerifyEmailSubject
}
} | boolean
forgotPassword?: {
generateEmailHTML?: GenerateForgotPasswordEmailHTML
generateEmailSubject?: GenerateForgotPasswordEmailSubject

View File

@@ -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;

View File

@@ -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<Document>, PassportLocalModel<Document>{}
@@ -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<PayloadCollectionConfig> {
auth: DeepRequired<Auth>
upload: DeepRequired<Upload>
export interface CollectionConfig extends Omit<DeepRequired<PayloadCollectionConfig>, 'auth' | 'upload'> {
auth: Auth | boolean
upload: Upload | boolean
}
export type Collection = {

View File

@@ -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;
}

View File

@@ -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,
},

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
export default !!(
(typeof window !== 'undefined'
&& window.document && window.document.createElement)
);

View File

@@ -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);

View File

@@ -1 +1 @@
export default {};
export default () => {};