Merge pull request #46 from directus/activity

Activity
This commit is contained in:
Rijk van Zanten
2020-06-29 18:27:30 -04:00
committed by GitHub
24 changed files with 521 additions and 120 deletions

92
package-lock.json generated
View File

@@ -231,6 +231,14 @@
"@hapi/hoek": "^9.0.0"
}
},
"@phc/format": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-0.5.0.tgz",
"integrity": "sha512-JWtZ5P1bfXU0bAtTzCpOLYHDXuxSVdtL/oqz4+xa97h8w9E5IlVN333wugXVFv8vZ1hbXObKQf1ptXmFFcMByg==",
"requires": {
"safe-buffer": "^5.1.2"
}
},
"@slynova/flydrive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@slynova/flydrive/-/flydrive-1.0.1.tgz",
@@ -267,12 +275,6 @@
"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",
@@ -647,6 +649,48 @@
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
"argon2": {
"version": "0.26.2",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.26.2.tgz",
"integrity": "sha512-Tk9I/r3KIHCIHU5x2UawKsPi+g7MByAYnUZghXztQDXRp/997P31wa4qvdvokTaFBpsu6jOZACd+2qkBGGssRA==",
"requires": {
"@phc/format": "^0.5.0",
"node-addon-api": "^2.0.0",
"node-pre-gyp": "^0.14.0"
},
"dependencies": {
"node-addon-api": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.1.tgz",
"integrity": "sha512-2WVfwRfIr1AVn3dRq4yRc2Hn35ND+mPJH6inC6bjpYCZVrpXPB4j3T6i//OGVfqVsR1t/X/axRulDsheq4F0LQ=="
},
"node-pre-gyp": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.1",
"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"
}
}
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -882,42 +926,6 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"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",

View File

@@ -29,7 +29,6 @@
"homepage": "https://github.com/directus/api-node#readme",
"devDependencies": {
"@types/atob": "^2.1.2",
"@types/bcrypt": "^3.0.0",
"@types/busboy": "^0.2.3",
"@types/express": "^4.17.6",
"@types/express-pino-logger": "^4.0.2",
@@ -68,8 +67,8 @@
"@slynova/flydrive": "^1.0.1",
"@slynova/flydrive-gcs": "^1.0.1",
"@slynova/flydrive-s3": "^1.0.1",
"argon2": "^0.26.2",
"atob": "^2.1.2",
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",
"busboy": "^0.3.1",
"camelcase": "^6.0.0",

View File

@@ -31,6 +31,7 @@ import notFoundHandler from './routes/not-found';
const app = express()
.disable('x-powered-by')
.set('trust proxy', true)
.use(logger())
.use(bodyParser.json())
.use(extractToken)

View File

@@ -0,0 +1,13 @@
/**
* Set req.collection for use in other middleware. Used as an alternative on validate-collection for
* system collections
*/
import asyncHandler from 'express-async-handler';
const useCollection = (collection: string) =>
asyncHandler(async (req, res, next) => {
req.collection = collection;
next();
});
export default useCollection;

View File

@@ -12,10 +12,10 @@ import asyncHandler from 'express-async-handler';
import { InvalidQueryException } from '../exceptions';
const validateQuery: RequestHandler = asyncHandler(async (req, res, next) => {
if (!req.params.collection) return next();
if (!res.locals.query) return next();
if (!req.collection) return next();
if (!req.query) return next();
const query: Query = res.locals.query;
const query: Query = req.query;
await Promise.all([
validateParams(req.params.collection, query),

View File

@@ -3,11 +3,13 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import { readActivities, readActivity } from '../services/activity';
import useCollection from '../middleware/use-collection';
const router = express.Router();
router.get(
'/',
useCollection('directus_activity'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -20,6 +22,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_activity'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {

View File

@@ -6,6 +6,8 @@ import * as AuthService from '../services/auth';
import grant from 'grant';
import getGrantConfig from '../utils/get-grant-config';
import getEmailFromProfile from '../utils/get-email-from-profile';
import { InvalidPayloadException } from '../exceptions/invalid-payload';
import * as ActivityService from '../services/activity';
const router = Router();
@@ -17,16 +19,21 @@ const loginSchema = Joi.object({
router.post(
'/authenticate',
asyncHandler(async (req, res) => {
await loginSchema.validateAsync(req.body);
const { error } = loginSchema.validate(req.body);
if (error) throw new InvalidPayloadException(error.message);
const { email, password } = req.body;
/**
* @TODO
* Make sure to validate the payload. AuthService.authenticate's password is optional which
* means there's a possible problem when req.body.password is undefined
*/
const { token, id } = await AuthService.authenticate(email, password);
const token = await AuthService.authenticate(email, password);
ActivityService.createActivity({
action: ActivityService.Action.AUTHENTICATE,
collection: 'directus_users',
item: id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: id,
});
return res.status(200).json({
data: { token },
@@ -34,7 +41,10 @@ router.post(
})
);
router.use('/sso', session({ secret: process.env.SECRET, saveUninitialized: true, resave: false }));
router.use(
'/sso',
session({ secret: process.env.SECRET, saveUninitialized: false, resave: false })
);
router.use(grant.express()(getGrantConfig()));
@@ -43,7 +53,16 @@ router.get(
asyncHandler(async (req, res) => {
const email = getEmailFromProfile(req.params.provider, req.session.grant.response.profile);
const token = await AuthService.authenticate(email);
const { token, id } = await AuthService.authenticate(email);
ActivityService.createActivity({
action: ActivityService.Action.AUTHENTICATE,
collection: 'directus_users',
item: id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: id,
});
return res.status(200).json({
data: { token },

View File

@@ -3,22 +3,36 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as CollectionPresetsService from '../services/collection-presets';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
router.post(
'/',
useCollection('directus_collection_presets'),
asyncHandler(async (req, res) => {
const records = await CollectionPresetsService.createCollectionPreset(
const record = await CollectionPresetsService.createCollectionPreset(
req.body,
res.locals.query
);
return res.json({ data: records });
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: 'directus_collection_presets',
item: record.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: record });
})
);
router.get(
'/',
useCollection('directus_collection_presets'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -29,6 +43,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_collection_presets'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -42,20 +57,42 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_collection_presets'),
asyncHandler(async (req, res) => {
const records = await CollectionPresetsService.updateCollectionPreset(
const record = await CollectionPresetsService.updateCollectionPreset(
req.params.pk,
req.body,
res.locals.query
);
return res.json({ data: records });
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: 'directus_collection_presets',
item: record.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: record });
})
);
router.delete(
'/:pk',
useCollection('directus_collection_presets'),
asyncHandler(async (req, res) => {
await CollectionPresetsService.deleteCollectionPreset(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: 'directus_collection_presets',
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -6,12 +6,15 @@ import validateQuery from '../middleware/validate-query';
import * as FilesService from '../services/files';
import logger from '../logger';
import { InvalidPayloadException } from '../exceptions';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
const multipartHandler = (operation: 'create' | 'update') =>
asyncHandler(async (req, res, next) => {
const busboy = new Busboy({ headers: req.headers });
const savedFiles: Record<string, any> = [];
/**
* The order of the fields in multipart/form-data is important. We require that all fields
@@ -52,9 +55,31 @@ const multipartHandler = (operation: 'create' | 'update') =>
try {
if (operation === 'create') {
await FilesService.createFile(payload, fileStream);
const file = await FilesService.createFile(payload, fileStream);
ActivityService.createActivity({
action: ActivityService.Action.UPLOAD,
collection: 'directus_files',
item: file.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
savedFiles.push(file);
} else {
await FilesService.updateFile(req.params.pk, payload, fileStream);
const file = await FilesService.updateFile(req.params.pk, payload, fileStream);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: 'directus_files',
item: file.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
savedFiles.push(file);
}
} catch (err) {
busboy.emit('error', err);
@@ -66,16 +91,17 @@ const multipartHandler = (operation: 'create' | 'update') =>
});
busboy.on('finish', () => {
res.status(200).end();
res.status(200).json({ data: savedFiles });
});
return req.pipe(busboy);
});
router.post('/', multipartHandler('create'));
router.post('/', useCollection('directus_files'), multipartHandler('create'));
router.get(
'/',
useCollection('directus_files'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -86,6 +112,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_files'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -96,19 +123,32 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_files'),
asyncHandler(async (req, res, next) => {
let file: Record<string, any>;
if (req.is('multipart/form-data')) {
await multipartHandler('update')(req, res, next);
file = await multipartHandler('update')(req, res, next);
} else {
await FilesService.updateFile(req.params.pk, req.body);
file = await FilesService.updateFile(req.params.pk, req.body);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: 'directus_files',
item: file.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
}
return res.status(200).end();
return res.status(200).json({ data: file });
})
);
router.delete(
'/:pk',
useCollection('directus_files'),
asyncHandler(async (req, res) => {
await FilesService.deleteFile(req.params.pk);
return res.status(200).end();

View File

@@ -2,20 +2,36 @@ import express from 'express';
import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import useCollection from '../middleware/use-collection';
import * as FoldersService from '../services/folders';
import * as ActivityService from '../services/activity';
import * as PayloadService from '../services/payload';
const router = express.Router();
router.post(
'/',
useCollection('directus_folders'),
asyncHandler(async (req, res) => {
const records = await FoldersService.createFolder(req.body, res.locals.query);
return res.json({ data: records });
const payload = await PayloadService.processValues('create', req.collection, req.body);
const record = await FoldersService.createFolder(payload, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: record.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: record });
})
);
router.get(
'/',
useCollection('directus_folders'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -26,6 +42,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_folders'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -36,20 +53,40 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_folders'),
asyncHandler(async (req, res) => {
const records = await FoldersService.updateFolder(
req.params.pk,
req.body,
res.locals.query
);
return res.json({ data: records });
const payload = await PayloadService.processValues('create', req.collection, req.body);
const record = await FoldersService.updateFolder(req.params.pk, payload, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: record.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: record });
})
);
router.delete(
'/:pk',
useCollection('directus_folders'),
asyncHandler(async (req, res) => {
await FoldersService.deleteFolder(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -7,6 +7,7 @@ import validateSingleton from '../middleware/validate-singleton';
import validateQuery from '../middleware/validate-query';
import * as MetaService from '../services/meta';
import * as PayloadService from '../services/payload';
import * as ActivityService from '../services/activity';
const router = express.Router();
@@ -16,8 +17,19 @@ router.post(
validateSingleton,
asyncHandler(async (req, res) => {
const payload = await PayloadService.processValues('create', req.collection, req.body);
await createItem(req.params.collection, payload);
res.status(200).end();
const item = await createItem(req.params.collection, payload);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
/** @TODO don't forget to use real primary key here */
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
res.json({ data: item });
})
);
@@ -56,8 +68,18 @@ router.patch(
validateCollection,
asyncHandler(async (req, res) => {
const payload = await PayloadService.processValues('update', req.collection, req.body);
await updateItem(req.params.collection, req.params.pk, payload);
return res.status(200).end();
const item = await updateItem(req.params.collection, req.params.pk, payload);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
@@ -66,6 +88,16 @@ router.delete(
validateCollection,
asyncHandler(async (req, res) => {
await deleteItem(req.params.collection, req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -3,29 +3,44 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as PermissionsService from '../services/permissions';
import * as ActivityService from '../services/activity';
import useCollection from '../middleware/use-collection';
const router = express.Router();
router.post(
'/',
useCollection('directus_permissions'),
asyncHandler(async (req, res) => {
const records = await PermissionsService.createPermission(req.body, res.locals.query);
return res.json({ data: records });
const item = await PermissionsService.createPermission(req.body, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/',
useCollection('directus_permissions'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
const records = await PermissionsService.readPermissions(res.locals.query);
return res.json({ data: records });
const item = await PermissionsService.readPermissions(res.locals.query);
return res.json({ data: item });
})
);
router.get(
'/:pk',
useCollection('directus_permissions'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -36,20 +51,42 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_permissions'),
asyncHandler(async (req, res) => {
const records = await PermissionsService.updatePermission(
const item = await PermissionsService.updatePermission(
req.params.pk,
req.body,
res.locals.query
);
return res.json({ data: records });
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.delete(
'/:pk',
useCollection('directus_permissions'),
asyncHandler(async (req, res) => {
await PermissionsService.deletePermission(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -3,19 +3,33 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as RelationsService from '../services/relations';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
router.post(
'/',
useCollection('directus_relations'),
asyncHandler(async (req, res) => {
const records = await RelationsService.createRelation(req.body, res.locals.query);
return res.json({ data: records });
const item = await RelationsService.createRelation(req.body, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/',
useCollection('directus_relations'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -26,6 +40,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_relations'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -36,20 +51,42 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_relations'),
asyncHandler(async (req, res) => {
const records = await RelationsService.updateRelation(
const item = await RelationsService.updateRelation(
req.params.pk,
req.body,
res.locals.query
);
return res.json({ data: records });
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.delete(
'/:pk',
useCollection('directus_relations'),
asyncHandler(async (req, res) => {
await RelationsService.deleteRelation(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -3,11 +3,13 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as RevisionsService from '../services/revisions';
import useCollection from '../middleware/use-collection';
const router = express.Router();
router.get(
'/',
useCollection('directus_revisions'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -18,6 +20,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_revisions'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {

View File

@@ -3,19 +3,33 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as RolesService from '../services/roles';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
router.post(
'/',
useCollection('directus_roles'),
asyncHandler(async (req, res) => {
const records = await RolesService.createRole(req.body, res.locals.query);
return res.json({ data: records });
const item = await RolesService.createRole(req.body, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/',
useCollection('directus_roles'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -26,6 +40,7 @@ router.get(
router.get(
'/:pk',
useCollection('directus_roles'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -36,16 +51,38 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_roles'),
asyncHandler(async (req, res) => {
const records = await RolesService.updateRole(req.params.pk, req.body, res.locals.query);
return res.json({ data: records });
const item = await RolesService.updateRole(req.params.pk, req.body, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.delete(
'/:pk',
useCollection('directus_roles'),
asyncHandler(async (req, res) => {
await RolesService.deleteRole(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -3,11 +3,13 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as SettingsService from '../services/settings';
import useCollection from '../middleware/use-collection';
const router = express.Router();
router.get(
'/',
useCollection('directus_settings'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -18,6 +20,7 @@ router.get(
router.patch(
'/',
useCollection('directus_settings'),
asyncHandler(async (req, res) => {
const records = await SettingsService.updateSettings(
req.params.pk /** @TODO Singleton */,

View File

@@ -5,29 +5,54 @@ import validateQuery from '../middleware/validate-query';
import * as UsersService from '../services/users';
import Joi from '@hapi/joi';
import { InvalidPayloadException } from '../exceptions';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
router.post(
'/',
useCollection('directus_users'),
asyncHandler(async (req, res) => {
const records = await UsersService.createUser(req.body, res.locals.query);
return res.json({ data: records });
const item = await UsersService.createUser(req.body, res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/',
useCollection('directus_users'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
const records = await UsersService.readUsers(res.locals.query);
return res.json({ data: records });
const item = await UsersService.readUsers(res.locals.query);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/:pk',
useCollection('directus_users'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
@@ -38,6 +63,7 @@ router.get(
router.patch(
'/:pk',
useCollection('directus_users'),
asyncHandler(async (req, res) => {
const records = await UsersService.updateUser(req.params.pk, req.body, res.locals.query);
return res.json({ data: records });
@@ -46,8 +72,19 @@ router.patch(
router.delete(
'/:pk',
useCollection('directus_users'),
asyncHandler(async (req, res) => {
await UsersService.deleteUser(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);
@@ -59,6 +96,7 @@ const inviteSchema = Joi.object({
router.post(
'/invite',
useCollection('directus_users'),
asyncHandler(async (req, res) => {
const { error } = inviteSchema.validate(req.body);
if (error) throw new InvalidPayloadException(error.message);
@@ -74,6 +112,7 @@ const acceptInviteSchema = Joi.object({
router.post(
'/invite/accept',
useCollection('directus_users'),
asyncHandler(async (req, res) => {
const { error } = acceptInviteSchema.validate(req.body);
if (error) throw new InvalidPayloadException(error.message);

View File

@@ -3,53 +3,86 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as WebhooksService from '../services/webhooks';
import useCollection from '../middleware/use-collection';
import * as ActivityService from '../services/activity';
const router = express.Router();
router.post(
'/',
useCollection('directus_webhooks'),
asyncHandler(async (req, res) => {
const records = await WebhooksService.createWebhook(req.body, res.locals.query);
return res.json({ data: records });
const item = await WebhooksService.createWebhook(req.body, req.query);
ActivityService.createActivity({
action: ActivityService.Action.CREATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.get(
'/',
useCollection('directus_webhooks'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
const records = await WebhooksService.readWebhooks(res.locals.query);
const records = await WebhooksService.readWebhooks(req.query);
return res.json({ data: records });
})
);
router.get(
'/:pk',
useCollection('directus_webhooks'),
sanitizeQuery,
validateQuery,
asyncHandler(async (req, res) => {
const record = await WebhooksService.readWebhook(req.params.pk, res.locals.query);
const record = await WebhooksService.readWebhook(req.params.pk, req.query);
return res.json({ data: record });
})
);
router.patch(
'/:pk',
useCollection('directus_webhooks'),
asyncHandler(async (req, res) => {
const records = await WebhooksService.updateWebhook(
req.params.pk,
req.body,
res.locals.query
);
return res.json({ data: records });
const item = await WebhooksService.updateWebhook(req.params.pk, req.body, req.query);
ActivityService.createActivity({
action: ActivityService.Action.UPDATE,
collection: req.collection,
item: item.id,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.json({ data: item });
})
);
router.delete(
'/:pk',
useCollection('directus_webhooks'),
asyncHandler(async (req, res) => {
await WebhooksService.deleteWebhook(req.params.pk);
ActivityService.createActivity({
action: ActivityService.Action.DELETE,
collection: req.collection,
item: req.params.pk,
ip: req.ip,
user_agent: req.get('user-agent'),
action_by: req.user,
});
return res.status(200).end();
})
);

View File

@@ -1,10 +1,24 @@
import { Query } from '../types/query';
import * as ItemsService from './items';
export const readActivities = async (query: Query) => {
export enum Action {
CREATE = 'create',
UPDATE = 'update',
DELETE = 'delete',
REVERT = 'revert',
COMMENT = 'comment',
UPLOAD = 'upload',
AUTHENTICATE = 'authenticate',
}
export const createActivity = async (data: Record<string, any>, query?: Query) => {
return await ItemsService.createItem('directus_activity', data, query);
};
export const readActivities = async (query?: Query) => {
return await ItemsService.readItems('directus_activity', query);
};
export const readActivity = async (pk: string | number, query: Query) => {
export const readActivity = async (pk: string | number, query?: Query) => {
return await ItemsService.readItem('directus_activity', pk, query);
};

View File

@@ -1,6 +1,6 @@
import database from '../database';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import argon2 from 'argon2';
import { InvalidCredentialsException } from '../exceptions';
export const authenticate = async (email: string, password?: string) => {
@@ -21,7 +21,7 @@ export const authenticate = async (email: string, password?: string) => {
* email to leak anywhere else.. We might have to make a dedicated "copy" of this function to
* signal the difference
*/
if (password !== undefined && (await bcrypt.compare(password, user.password)) === false) {
if (password !== undefined && (await argon2.verify(password, user.password)) === false) {
throw new InvalidCredentialsException();
}
@@ -38,5 +38,5 @@ export const authenticate = async (email: string, password?: string) => {
expiresIn: process.env.ACCESS_TOKEN_EXPIRY_TIME,
});
return token;
return { token, id: user.id };
};

View File

@@ -45,7 +45,7 @@ export const createFile = async (
}
await storage.disk(data.storage).put(data.filename_disk, stream as any);
await ItemsService.createItem('directus_files', payload, query);
return await ItemsService.createItem('directus_files', payload, query);
};
export const readFiles = async (query: Query) => {
@@ -64,13 +64,17 @@ export const updateFile = async (
query?: Query
) => {
const payload = await PayloadService.processValues('update', 'directus_files', data);
await ItemsService.updateItem('directus_files', pk, payload, query);
/**
* @TODO
* Handle changes in storage adapter -> going from local to S3 needs to delete from one, upload to the other
*/
/**
* @TODO
* Extract metadata here too
*/
if (stream) {
const file = await database
.select('storage', 'filename_disk')
@@ -81,6 +85,8 @@ export const updateFile = async (
// @todo type of stream in flydrive is wrong: https://github.com/Slynova-Org/flydrive/issues/145
await storage.disk(file.storage).put(file.filename_disk, stream as any);
}
return await ItemsService.updateItem('directus_files', pk, payload, query);
};
export const deleteFile = async (pk: string | number) => {

View File

@@ -76,7 +76,8 @@ export const updateItem = async (
data: Record<string, any>,
query: Query = {}
) => {
return await database(collection).update(data).where({ id: pk });
const result = await database(collection).update(data).where({ id: pk }).returning('id');
return readItem(collection, result[0], query);
};
export const deleteItem = async (collection: string, pk: number | string) => {

View File

@@ -6,7 +6,7 @@
*/
import { FieldInfo } from '../types/field';
import bcrypt from 'bcrypt';
import argon2 from 'argon2';
import { v4 as uuidv4 } from 'uuid';
import database from '../database';
import { clone } from 'lodash';
@@ -51,8 +51,10 @@ async function processField(
}
}
async function genHash(value: string | number) {
return await bcrypt.hash(value, Number(process.env.SALT_ROUNDS));
async function genHash(value?: string | number) {
if (!value) return;
return await argon2.hash(String(value));
}
async function genUUID(operation: 'create' | 'update') {

View File

@@ -3,7 +3,7 @@ import * as ItemsService from './items';
import jwt from 'jsonwebtoken';
import { sendInviteMail } from '../mail';
import database from '../database';
import bcrypt from 'bcrypt';
import argon2 from 'argon2';
import * as PayloadService from '../services/payload';
import { InvalidPayloadException } from '../exceptions';
@@ -55,7 +55,7 @@ export const acceptInvite = async (token: string, password: string) => {
throw new InvalidPayloadException(`Email address ${email} hasn't been invited.`);
}
const passwordHashed = await bcrypt.hash(password, Number(process.env.SALT_ROUNDS));
const passwordHashed = await argon2.hash(password);
await database('directus_users')
.update({ password: passwordHashed, status: 'active' })