mirror of
https://github.com/directus/directus.git
synced 2026-01-29 23:47:57 -05:00
@@ -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
47
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
65
src/middleware/process-payload.ts
Normal file
65
src/middleware/process-payload.ts
Normal 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;
|
||||
@@ -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();
|
||||
|
||||
18
src/types/express.d.ts
vendored
18
src/types/express.d.ts
vendored
@@ -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
18
src/types/field.ts
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user