diff --git a/src/auth/operations/forgotPassword.ts b/src/auth/operations/forgotPassword.ts index 5699cc59f7e..86b83b1a1dd 100644 --- a/src/auth/operations/forgotPassword.ts +++ b/src/auth/operations/forgotPassword.ts @@ -1,11 +1,12 @@ import crypto from 'crypto'; +import { AfterForgotPasswordHook, BeforeOperationHook } from '../../collections/config/types'; import { APIError } from '../../errors'; async function forgotPassword(incomingArgs) { const { config, sendEmail: email } = this; if (!Object.prototype.hasOwnProperty.call(incomingArgs.data, 'email')) { - throw new APIError('Missing email.'); + throw new APIError('Missing email.', 400); } let args = incomingArgs; @@ -14,7 +15,7 @@ async function forgotPassword(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ @@ -38,7 +39,7 @@ async function forgotPassword(incomingArgs) { // Forget password // ///////////////////////////////////// - let token = crypto.randomBytes(20); + let token: string | Buffer = crypto.randomBytes(20); token = token.toString('hex'); const user = await Model.findOne({ email: data.email.toLowerCase() }); @@ -90,7 +91,7 @@ async function forgotPassword(incomingArgs) { // afterForgotPassword - Collection // ///////////////////////////////////// - await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => { + await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook: AfterForgotPasswordHook, hook: AfterForgotPasswordHook) => { await priorHook; await hook({ args }); }, Promise.resolve()); diff --git a/src/auth/operations/login.ts b/src/auth/operations/login.ts index 5a56a2bb07b..9deb06d1fc9 100644 --- a/src/auth/operations/login.ts +++ b/src/auth/operations/login.ts @@ -3,6 +3,7 @@ import { AuthenticationError, LockedAuth } from '../../errors'; import getCookieExpiration from '../../utilities/getCookieExpiration'; import isLocked from '../isLocked'; import removeInternalFields from '../../utilities/removeInternalFields'; +import { BeforeLoginHook, BeforeOperationHook } from '../../collections/config/types'; async function login(incomingArgs) { const { config, operations, secret } = this; @@ -13,7 +14,7 @@ async function login(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ diff --git a/src/auth/operations/refresh.ts b/src/auth/operations/refresh.ts index cd533166dfb..5a2cea9c181 100644 --- a/src/auth/operations/refresh.ts +++ b/src/auth/operations/refresh.ts @@ -1,4 +1,5 @@ import jwt from 'jsonwebtoken'; +import { BeforeOperationHook } from '../../collections/config/types'; import { Forbidden } from '../../errors'; import getCookieExpiration from '../../utilities/getCookieExpiration'; @@ -9,7 +10,7 @@ async function refresh(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index f1519d414d8..50799d32b52 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -1,14 +1,9 @@ -import { Access, Hook } from '../../config/types'; +/* eslint-disable no-use-before-define */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Access } from '../../config/types'; import { Field } from '../../fields/config/types'; import { PayloadRequest } from '../../express/types/payloadRequest'; -export type ImageSize = { - name: string, - width: number, - height: number, - crop: string, // comes from sharp package -}; - export type Collection = { slug: string; labels?: { @@ -22,18 +17,17 @@ export type Collection = { components?: any; }; hooks?: { - beforeOperation?: Hook[]; - beforeValidate?: Hook[]; - beforeChange?: Hook[]; - afterChange?: Hook[]; - beforeRead?: Hook[]; - afterRead?: Hook[]; - beforeDelete?: Hook[]; - afterDelete?: Hook[]; - beforeLogin?: Hook[]; - afterLogin?: Hook[]; - afterForgotPassword?: Hook[]; - forgotPassword?: Hook[]; + beforeOperation?: BeforeOperationHook[]; + beforeValidate?: BeforeValidateHook[]; + beforeChange?: BeforeChangeHook[]; + afterChange?: AfterChangeHook[]; + beforeRead?: BeforeReadHook[]; + afterRead?: AfterReadHook[]; + beforeDelete?: BeforeDeleteHook[]; + afterDelete?: AfterDeleteHook[]; + beforeLogin?: BeforeLoginHook[]; + afterLogin?: AfterLoginHook[]; + afterForgotPassword?: AfterForgotPasswordHook[]; }; access?: { create?: Access; @@ -59,11 +53,11 @@ export type Collection = { } | boolean; forgotPassword?: { - generateEmailHTML?: (args?: {token?: string, email?: string, req?: PayloadRequest}) => string, - generateEmailSubject?: (args?: {req?: PayloadRequest}) => string, + generateEmailHTML?: (args?: { token?: string, email?: string, req?: PayloadRequest }) => string, + generateEmailSubject?: (args?: { req?: PayloadRequest }) => string, } }; - config: {[key: string]: any}; + config: { [key: string]: any }; upload: { imageSizes: ImageSize[]; staticURL: string; @@ -71,3 +65,83 @@ export type Collection = { adminThumbnail?: string; }; }; + +export type ImageSize = { + name: string, + width: number, + height: number, + crop: string, // comes from sharp package +}; + +// Hooks + +export type HookOperationType = + | 'create' + | 'read' + | 'update' + | 'delete' + | 'refresh' + | 'login' + | 'forgotPassword'; + +export type BeforeOperationHook = (args?: { + args?: any; + operation: HookOperationType; +}) => any; + +export type BeforeValidateHook = (args?: { + data?: any; + req?: PayloadRequest; + operation: 'create' | 'update'; + originalDoc?: any; // undefined on 'create' operation +}) => any; + +export type BeforeChangeHook = (args?: { + data: any; + req: PayloadRequest; + operation: 'create' | 'update' + originalDoc?: any; // undefined on 'create' operation +}) => any; + +export type AfterChangeHook = (args?: { + doc: any; + req: PayloadRequest; + operation: 'create' | 'update'; +}) => any; + +export type BeforeReadHook = (args?: { + doc: any; + req: PayloadRequest; + query: { [key: string]: any }; +}) => any; + +export type AfterReadHook = (args?: { + doc: any; + req: PayloadRequest; + query: { [key: string]: any }; +}) => any; + +export type BeforeDeleteHook = (args?: { + req: PayloadRequest; + id: string; +}) => any; + +export type AfterDeleteHook = (args?: { + req: PayloadRequest; + id: string; + doc: any; +}) => any; + +export type BeforeLoginHook = (args?: { + req: PayloadRequest; +}) => any; + +export type AfterLoginHook = (args?: { + req: PayloadRequest; + user: any; + token: string; +}) => any; + +export type AfterForgotPasswordHook = (args?: { + args?: any; +}) => any; diff --git a/src/collections/init.ts b/src/collections/init.ts index e004dc4cefa..fb083935153 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -2,13 +2,17 @@ import mongoose from 'mongoose'; import express from 'express'; import passport from 'passport'; import passportLocalMongoose from 'passport-local-mongoose'; -const LocalStrategy = require('passport-local').Strategy; +import Passport from 'passport-local'; +import { UpdateQuery } from 'mongodb'; import apiKeyStrategy from '../auth/strategies/apiKey'; import buildSchema from './buildSchema'; import bindCollectionMiddleware from './bindCollection'; +import { Collection } from './config/types'; -function registerCollections() { - this.config.collections = this.config.collections.map((collection) => { +const LocalStrategy = Passport.Strategy; + +export default function registerCollections(): void { + this.config.collections = this.config.collections.map((collection: Collection) => { const formattedCollection = collection; const schema = buildSchema(formattedCollection, this.config); @@ -32,7 +36,8 @@ function registerCollections() { }, cb); } - const updates = { $inc: { loginAttempts: 1 } }; + type LoginSchema = { loginAttempts: number; }; + const updates: UpdateQuery = { $inc: { loginAttempts: 1 } }; // Lock the account if at max attempts and not already locked if (this.loginAttempts + 1 >= maxLoginAttempts && !this.isLocked) { updates.$set = { lockUntil: Date.now() + lockTime }; @@ -151,5 +156,3 @@ function registerCollections() { return formattedCollection; }); } - -export default registerCollections; diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index fc8d96647b9..44a576227ab 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -12,6 +12,7 @@ import getImageSize from '../../uploads/getImageSize'; import imageMIMETypes from '../../uploads/imageMIMETypes'; import sendVerificationEmail from '../../auth/sendVerificationEmail'; +import { AfterChangeHook, BeforeOperationHook, BeforeValidateHook } from '../config/types'; async function create(incomingArgs) { const { performFieldOperations, config } = this; @@ -22,7 +23,7 @@ async function create(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ @@ -73,7 +74,7 @@ async function create(incomingArgs) { // beforeValidate - Collections // ///////////////////////////////////// - await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => { + await collectionConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => { await priorHook; data = (await hook({ @@ -214,7 +215,7 @@ async function create(incomingArgs) { // afterChange - Collection // ///////////////////////////////////// - await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => { + await collectionConfig.hooks.afterChange.reduce(async (priorHook: AfterChangeHook, hook: AfterChangeHook) => { await priorHook; result = await hook({ diff --git a/src/collections/operations/delete.ts b/src/collections/operations/delete.ts index 6e5d7d34ab8..32955b2ab14 100644 --- a/src/collections/operations/delete.ts +++ b/src/collections/operations/delete.ts @@ -5,6 +5,7 @@ import removeInternalFields from '../../utilities/removeInternalFields'; import { NotFound, Forbidden, ErrorDeletingFile } from '../../errors'; import executeAccess from '../../auth/executeAccess'; import fileExists from '../../uploads/fileExists'; +import { BeforeOperationHook } from '../config/types'; async function deleteQuery(incomingArgs) { let args = incomingArgs; @@ -13,7 +14,7 @@ async function deleteQuery(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ diff --git a/src/collections/operations/find.ts b/src/collections/operations/find.ts index 321394d6525..cdacf3075be 100644 --- a/src/collections/operations/find.ts +++ b/src/collections/operations/find.ts @@ -1,5 +1,6 @@ import executeAccess from '../../auth/executeAccess'; import removeInternalFields from '../../utilities/removeInternalFields'; +import { BeforeOperationHook, BeforeReadHook } from '../config/types'; async function find(incomingArgs) { let args = incomingArgs; @@ -8,7 +9,7 @@ async function find(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ diff --git a/src/collections/operations/findByID.ts b/src/collections/operations/findByID.ts index e4c14b3a390..f52b404f2b3 100644 --- a/src/collections/operations/findByID.ts +++ b/src/collections/operations/findByID.ts @@ -1,5 +1,6 @@ -/* eslint-disable no-underscore-dangle */ import memoize from 'micro-memoize'; +import { BeforeOperationHook } from '../config/types'; +/* eslint-disable no-underscore-dangle */ import removeInternalFields from '../../utilities/removeInternalFields'; import { Forbidden, NotFound } from '../../errors'; import executeAccess from '../../auth/executeAccess'; @@ -11,7 +12,7 @@ async function findByID(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index f28a426c49f..b79ef4b5c79 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -1,6 +1,7 @@ import httpStatus from 'http-status'; import deepmerge from 'deepmerge'; import path from 'path'; +import { BeforeOperationHook, BeforeChangeHook, BeforeValidateHook } from '../config/types'; import removeInternalFields from '../../utilities/removeInternalFields'; import overwriteMerge from '../../utilities/overwriteMerge'; @@ -12,6 +13,7 @@ import getSafeFilename from '../../uploads/getSafeFilename'; import resizeAndSave from '../../uploads/imageResizer'; + async function update(incomingArgs) { let args = incomingArgs; @@ -19,7 +21,7 @@ async function update(incomingArgs) { // beforeOperation - Collection // ///////////////////////////////////// - await args.collection.config.hooks.beforeOperation.reduce(async (priorHook, hook) => { + await args.collection.config.hooks.beforeOperation.reduce(async (priorHook: BeforeOperationHook, hook: BeforeOperationHook) => { await priorHook; args = (await hook({ @@ -111,7 +113,7 @@ async function update(incomingArgs) { // beforeValidate - Collection // ///////////////////////////////////// - await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => { + await collectionConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => { await priorHook; data = (await hook({ @@ -140,7 +142,7 @@ async function update(incomingArgs) { // beforeChange - Collection // ///////////////////////////////////// - await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => { + await collectionConfig.hooks.beforeChange.reduce(async (priorHook: BeforeChangeHook, hook: BeforeChangeHook) => { await priorHook; data = (await hook({ diff --git a/src/config/types.ts b/src/config/types.ts index 5dada444455..2bd6976a8e9 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -47,17 +47,18 @@ export type Access = (args?: any) => boolean; export type Config = { admin?: { - user?: string + user?: string; meta?: { - titleSuffix?: string - ogImage?: string - favicon?: string + titleSuffix?: string; + ogImage?: string; + favicon?: string; } - disable?: boolean + disable?: boolean; + indexHTML?: string; }; collections?: Collection[]; globals?: Global[]; - serverURL?: string; + serverURL: string; cookiePrefix?: string; csrf?: string[]; cors?: string[]; @@ -68,7 +69,7 @@ export type Config = { graphQL?: string; graphQLPlayground?: string; }; - express: { + express?: { json: { limit?: number } diff --git a/src/globals/operations/update.ts b/src/globals/operations/update.ts index 3256a63baa7..9b1f9acca0e 100644 --- a/src/globals/operations/update.ts +++ b/src/globals/operations/update.ts @@ -2,6 +2,7 @@ import deepmerge from 'deepmerge'; import overwriteMerge from '../../utilities/overwriteMerge'; import executeAccess from '../../auth/executeAccess'; import removeInternalFields from '../../utilities/removeInternalFields'; +import { AfterChangeHook, BeforeValidateHook } from '../../collections/config/types'; async function update(args) { const { globals: { Model } } = this; @@ -53,7 +54,7 @@ async function update(args) { // 3. Execute before validate collection hooks // ///////////////////////////////////// - await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => { + await globalConfig.hooks.beforeValidate.reduce(async (priorHook: BeforeValidateHook, hook: BeforeValidateHook) => { await priorHook; data = (await hook({ @@ -123,7 +124,7 @@ async function update(args) { // 9. Execute after global hook // ///////////////////////////////////// - await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => { + await globalConfig.hooks.afterChange.reduce(async (priorHook: AfterChangeHook, hook: AfterChangeHook) => { await priorHook; global = await hook({