diff --git a/package-lock.json b/package-lock.json index a1acb2d..0109487 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,8 @@ "trivialperms": "^2.0.0-beta.0", "ts-essentials": "^10.0.0", "zod": "^3.23.8", - "zod-express-middleware": "^1.4.0" + "zod-express": "^0.0.8", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@ckpack/vue-color": "^1.3.0", @@ -7325,14 +7326,25 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-express-middleware": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/zod-express-middleware/-/zod-express-middleware-1.4.0.tgz", - "integrity": "sha512-C1pBbwbuotitG1L3I1cr9QD/nuepHAdZEUbVn7y1o2cJq0oaUuS7gTVGby1+DGHL4t2P4eEZKCX0QQDv6hEs3A==", + "node_modules/zod-express": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/zod-express/-/zod-express-0.0.8.tgz", + "integrity": "sha512-zR0EQ6P12zox7v5piKTQCNIhgraVv8ev3Gkt5wxUlxt/3KUkEVrDyCYpfE0O5B2mTGAcfUktdP42dEV6rEKZYA==", "peerDependencies": { "@types/express": "^4.17.12", - "express": "^4.17.1", - "zod": "^3.2.0" + "express": "^4.18.2", + "zod": "^3.21.4" + } + }, + "node_modules/zod-validation-error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz", + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" } } } diff --git a/package.json b/package.json index 5f27cbb..da672af 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "trivialperms": "^2.0.0-beta.0", "ts-essentials": "^10.0.0", "zod": "^3.23.8", - "zod-express-middleware": "^1.4.0" + "zod-express": "^0.0.8", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@ckpack/vue-color": "^1.3.0", @@ -88,7 +89,7 @@ "vue-router": "^4.1.6" }, "overrides": { - "zod-express-middleware": { + "zod-express": { "express": "$express" } }, diff --git a/src/server/engines/validation/express.ts b/src/server/engines/validation/express.ts new file mode 100644 index 0000000..31729ff --- /dev/null +++ b/src/server/engines/validation/express.ts @@ -0,0 +1,43 @@ +// --------------------------------------------------------------------------------------------------------------------- +// Express Validation Tools +// --------------------------------------------------------------------------------------------------------------------- + +import { Request, Response, NextFunction } from 'express'; + +import { fromError } from 'zod-validation-error'; + +// --------------------------------------------------------------------------------------------------------------------- + +export function validationErrorHandler(err : any, req : Request, res : Response, next : NextFunction) : void +{ + if(Array.isArray(err)) + { + const errors = err.map((errObj) => + { + return { + ...errObj, + errors: fromError(errObj.errors) + }; + }); + + if(errors.length > 0) + { + const errorMsg = errors.map((errObj) => + { + return `${ errObj.type }: ${ errObj.errors.toString() }`; + }); + + res.status(422).json({ + message: `Request Validation Failed: ${ errorMsg.join('\n') }`, + errors + }); + + // Return to not call next + return; + } + } + + next(err); +} + +// --------------------------------------------------------------------------------------------------------------------- diff --git a/src/server/routes/accounts.ts b/src/server/routes/accounts.ts index 5dc7acb..b73a493 100644 --- a/src/server/routes/accounts.ts +++ b/src/server/routes/accounts.ts @@ -3,7 +3,7 @@ //---------------------------------------------------------------------------------------------------------------------- import express from 'express'; -import { processRequest } from 'zod-express-middleware'; +import { processRequest } from 'zod-express'; // Managers import * as accountMan from '../managers/account'; @@ -11,12 +11,14 @@ import * as permsMan from '../managers/permissions'; // Validation import * as AccountValidators from '../engines/validation/models/account'; +import { validationErrorHandler } from '../engines/validation/express'; // Utils import { ensureAuthenticated, errorHandler } from './utils'; // Logger import logging from '@strata-js/util-logging'; + const logger = logging.getLogger(module.filename); //---------------------------------------------------------------------------------------------------------------------- @@ -25,32 +27,53 @@ const router = express.Router(); //---------------------------------------------------------------------------------------------------------------------- -router.get('/', processRequest({ query: AccountValidators.AccountFilter }), async(req, resp) => -{ - resp.json((await accountMan.list(req.query)).map((accountObj) => - { - const { permissions, settings, groups, ...restAccount } = accountObj; - return restAccount; - })); -}); - -router.get('/:accountID', processRequest({ params: AccountValidators.UpdateParams }), async(req, resp) => -{ - const user = req.user; - const account = await accountMan.get(req.params.accountID); - - const sameOrAdmin = user && (user.id === req.params.accountID || permsMan.hasPerm(user, `Accounts/canViewDetails`)); - - if(req.isAuthenticated() && sameOrAdmin) +router.get( + '/', + processRequest({ query: AccountValidators.AccountFilter }, { passErrorToNext: true }), + async (req, resp) => { - resp.json(account); + resp.json((await accountMan.list(req.query)).map((accountObj) => + { + const { + permissions, + settings, + groups, + ...restAccount + } = accountObj; + return restAccount; + })); } - else +); + +router.get( + '/:accountID', + processRequest({ params: AccountValidators.UpdateParams }, { passErrorToNext: true }), + async (req, resp) => { - const { permissions, groups, settings, ...restAccount } = account; - resp.json(restAccount); + const user = req.user; + const account = await accountMan.get(req.params.accountID); + + const sameOrAdmin = user && (user.id === req.params.accountID || permsMan.hasPerm( + user, + `Accounts/canViewDetails` + )); + + if(req.isAuthenticated() && sameOrAdmin) + { + resp.json(account); + } + else + { + const { + permissions, + groups, + settings, + ...restAccount + } = account; + resp.json(restAccount); + } } -}); +); router.patch( '/:accountID', @@ -58,8 +81,8 @@ router.patch( processRequest({ params: AccountValidators.UpdateParams, body: AccountValidators.Account.partial({ id: true }) - }), - async(req, resp) => + }, { passErrorToNext: true }), + async (req, resp) => { // Update the account const newAccount = await accountMan.update(req.params.accountID, req.body); @@ -71,6 +94,7 @@ router.patch( // Error Handling //---------------------------------------------------------------------------------------------------------------------- +router.use(validationErrorHandler); router.use(errorHandler(logger)); //----------------------------------------------------------------------------------------------------------------------