Merge pull request #23 from directus/process-special

Process special
This commit is contained in:
Rijk van Zanten
2020-06-24 17:39:52 -04:00
committed by GitHub
8 changed files with 154 additions and 14 deletions

View File

@@ -20,6 +20,7 @@ DB_PASSWORD="psql1234"
SECRET="abcdef"
ACCESS_TOKEN_EXPIRY_TIME="15m"
REFRESH_TOKEN_EXPIRY_TIME="7d"
SALT_ROUNDS=10
####################################################################################################
# SSO (oAuth) Providers

47
package-lock.json generated
View File

@@ -109,6 +109,12 @@
"integrity": "sha512-8GAYQ1jDRUQkSpHzJUqXwAkYFOxuWAOGLhIR4aPd/Y/yL12Q/9m7LsKpHKlfKdNE/362Hc9wPI1Yh6opDfxVJg==",
"dev": true
},
"@types/bcrypt": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz",
"integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ==",
"dev": true
},
"@types/body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
@@ -598,6 +604,42 @@
}
}
},
"bcrypt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz",
"integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==",
"requires": {
"node-addon-api": "^3.0.0",
"node-pre-gyp": "0.15.0"
},
"dependencies": {
"node-pre-gyp": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz",
"integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==",
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.3",
"needle": "^2.5.0",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4.4.2"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"requires": {
"glob": "^7.1.3"
}
}
}
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -3592,6 +3634,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-addon-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz",
"integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg=="
},
"node-notifier": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz",

View File

@@ -29,6 +29,7 @@
"homepage": "https://github.com/directus/api-node#readme",
"devDependencies": {
"@types/atob": "^2.1.2",
"@types/bcrypt": "^3.0.0",
"@types/express": "^4.17.6",
"@types/express-session": "^1.17.0",
"@types/hapi__joi": "^17.1.2",
@@ -61,6 +62,7 @@
"dependencies": {
"@hapi/joi": "^17.1.1",
"atob": "^2.1.2",
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",
"dataloader": "^2.0.0",
"dotenv": "^8.2.0",

View File

@@ -5,6 +5,7 @@
*/
import DataLoader from 'dataloader';
import { FieldInfo } from './types/field';
import database from './database';
async function getFields(keys: { collection: string; field: string }[]) {
@@ -16,7 +17,7 @@ async function getFields(keys: { collection: string; field: string }[]) {
keys.map((key) => [key.collection, key.field])
);
return keys.map((key) =>
return keys.map<FieldInfo>((key) =>
records.find((record) => record.collection === key.collection && record.field === key.field)
);
}

View File

@@ -0,0 +1,65 @@
/**
* Will check the fields system table for any special operations that need to be done on the field
* value, this can include hashing the value or generating a UUID
*/
import { RequestHandler } from 'express';
import asyncHandler from 'express-async-handler';
import { FieldInfo } from '../types/field';
import bcrypt from 'bcrypt';
type Operation = 'create' | 'update';
/**
* @TODO
*
* This needs a bit of extra thinking.
* - There's a difference between update / create payload processing
* - Some processing types need the whole payload (slug)
* - What happens for fields that aren't in the payload but need to be set on create?
*/
const processPayload = (operation: Operation) => {
const middleware: RequestHandler = asyncHandler(async (req, res, next) => {
// Get the fields that have a special operation associated with them
const fieldsInPayload = Object.keys(req.body);
const fieldInfoForFields = await req.loaders.fields.loadMany(
fieldsInPayload.map((field) => ({
collection: req.collection,
field: field,
}))
);
const specialFields = fieldInfoForFields.filter((field) => {
if (field instanceof Error) return false;
return field.special !== null;
}) as FieldInfo[];
for (const field of specialFields) {
req.body[field.field] = await processField(req.collection, field, req.body, operation);
}
next();
});
return middleware;
};
async function processField(
collection: string,
field: FieldInfo,
payload: Record<string, any>,
operation: Operation
) {
switch (field.special) {
case 'hash':
return await hash(payload[field.field]);
}
}
async function hash(value: string | number) {
return await bcrypt.hash(value, Number(process.env.SALT_ROUNDS));
}
export default processPayload;

View File

@@ -2,15 +2,17 @@ import express from 'express';
import asyncHandler from 'express-async-handler';
import { createItem, readItems, readItem, updateItem, deleteItem } from '../services/items';
import sanitizeQuery from '../middleware/sanitize-query';
import collectionExists from '../middleware/collection-exists';
import validateCollection from '../middleware/validate-collection';
import validateQuery from '../middleware/validate-query';
import * as MetaService from '../services/meta';
import processPayload from '../middleware/process-payload';
const router = express.Router();
router.post(
'/:collection',
collectionExists,
validateCollection,
processPayload('create'),
asyncHandler(async (req, res) => {
await createItem(req.params.collection, req.body);
res.status(200).end();
@@ -19,7 +21,7 @@ router.post(
router.get(
'/:collection',
collectionExists,
validateCollection,
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -37,7 +39,7 @@ router.get(
router.get(
'/:collection/:pk',
collectionExists,
validateCollection,
asyncHandler(async (req, res) => {
const record = await readItem(req.params.collection, req.params.pk);
@@ -49,7 +51,7 @@ router.get(
router.patch(
'/:collection/:pk',
collectionExists,
validateCollection,
asyncHandler(async (req, res) => {
await updateItem(req.params.collection, req.params.pk, req.body);
return res.status(200).end();
@@ -58,7 +60,7 @@ router.patch(
router.delete(
'/:collection/:pk',
collectionExists,
validateCollection,
asyncHandler(async (req, res) => {
await deleteItem(req.params.collection, req.params.pk);
return res.status(200).end();

View File

@@ -2,12 +2,16 @@
* Custom properties on the req object in express
*/
declare namespace Express {
export interface Request {
token?: string;
user?: string;
role?: string;
collection?: string;
loaders?: any;
import createSystemLoaders from '../loaders';
declare global {
namespace Express {
export interface Request {
token?: string;
user?: string;
role?: string;
collection?: string;
loaders?: ReturnType<typeof createSystemLoaders>;
}
}
}

18
src/types/field.ts Normal file
View File

@@ -0,0 +1,18 @@
export type FieldInfo = {
id: number;
collection: string;
field: string;
special: string | null;
interface: string | null;
options: Record<string, any> | null;
locked: boolean;
required: boolean;
readonly: boolean;
hidden_detail: boolean;
hidden_browse: boolean;
sort: number | null;
width: string | null;
group: number | null;
note: string | null;
translation: null;
};