From bab99d471976829974f8e0d459eee0392bd9fd01 Mon Sep 17 00:00:00 2001 From: Facundo Date: Thu, 25 Nov 2021 23:43:19 -0300 Subject: [PATCH] feat: Workflow copy, export and import (#8) * workflow recipe. app core improves * standard * add db validation errors handler * workflow unscheduler (on remove workflow) * task destroy method. remove everything linked to a task. * stop throwing errors when events are not found * workflow CRUD methods updated * crud controllers backward compatible * added api version control using headers * ErrorHandler.ClientError dependency added --- core/app/api.js | 16 +- core/controllers/workflow/crud.js | 314 +++--------- core/controllers/workflow/crudv2.js | 294 ++++++++++++ core/controllers/workflow/index.js | 2 + core/controllers/workflow/job.js | 9 +- core/controllers/workflow/recipe.js | 67 +++ core/entity/event/index.js | 29 +- core/entity/workflow/schema.js | 1 + core/lib/async-controller.js | 42 ++ core/lib/async-middleware.js | 10 + core/service/events/task-trigger-by.js | 5 + core/service/events/workflow.js | 77 ++- core/service/job/index.js | 16 +- core/service/scheduler.js | 9 + core/service/task.js | 71 ++- .../password-hash/package-lock.json | 448 ++++++++++++++++++ misc/playground/password-hash/package.json | 2 +- 17 files changed, 1121 insertions(+), 291 deletions(-) create mode 100644 core/controllers/workflow/crudv2.js create mode 100644 core/controllers/workflow/recipe.js create mode 100644 core/lib/async-controller.js create mode 100644 core/lib/async-middleware.js create mode 100644 misc/playground/password-hash/package-lock.json diff --git a/core/app/api.js b/core/app/api.js index d22ee5ea..754e299c 100644 --- a/core/app/api.js +++ b/core/app/api.js @@ -56,17 +56,17 @@ module.exports = function () { // respond with error middleware server.use((req, res, next) => { - res.sendError = (error, next) => { - if (error.statusCode < 500) { - res.send(error.statusCode || 400, { - statusCode: error.statusCode, - message: error.message, - errors: error.errors + res.sendError = (err, next) => { + if (err instanceof ErrorHandler.ClientError || err.statusCode < 500) { + res.send(err.statusCode || 400, { + statusCode: err.statusCode, + message: err.message, + errors: err.errors }) } else { - logger.error(error) + logger.error(err) const handler = new ErrorHandler() - handler.sendExceptionAlert(error, req) + handler.sendExceptionAlert(err, req) res.send(500, 'Internal Server Error') } if (next) { next() } diff --git a/core/controllers/workflow/crud.js b/core/controllers/workflow/crud.js index eda733e7..305e834e 100644 --- a/core/controllers/workflow/crud.js +++ b/core/controllers/workflow/crud.js @@ -1,8 +1,10 @@ +const restify = require('restify') const graphlib = require('graphlib') const App = require('../../app') const TopicsConstants = require('../../constants/topics') const logger = require('../../lib/logger')('controller:workflow') +const audit = require('../../lib/audit') const router = require('../../router') // const audit = require('../../lib/audit') const Workflow = require('../../entity/workflow').Workflow @@ -12,202 +14,99 @@ const Tag = require('../../entity/tag').Entity const ACL = require('../../lib/acl'); const dbFilter = require('../../lib/db-filter'); +const crudv2 = require('./crudv2') + module.exports = function (server) { - // const crudTopic = TopicsConstants.workflow.crud + const CRUD_TOPIC = TopicsConstants.workflow.crud - // default middlewares - var middlewares = [ + server.get('/workflows', server.auth.bearerMiddleware, - router.resolve.customerNameToEntity({required: true}), - router.ensureCustomer - ] - - server.get( - '/workflows', - middlewares, - router.requireCredential('viewer') , - controller.fetch + router.resolve.customerSessionToEntity(), + router.ensureCustomer, + router.requireCredential('viewer'), + fetch ) - server.get( - '/workflows/:workflow', - middlewares, + server.get('/workflows/:workflow', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, router.requireCredential('viewer'), router.resolve.idToEntity({ param: 'workflow', required: true }), router.ensureAllowed({ entity: { name: 'workflow' } }) , - controller.get + (req, res, next) => { + res.send(200, req.workflow) + next() + } ) - server.post( - '/workflows', - middlewares, - router.requireCredential('admin') , - controller.create - // audit.afterCreate('workflow',{ display: 'name' }) // cannot audit bulk creation + server.post('/workflows', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, + router.requireCredential('admin'), + restify.plugins.conditionalHandler([ + { version: '2.9.0', handler: crudv2.create } + ]), + audit.afterCreate('workflow', { display: 'name', topic: CRUD_TOPIC }) ) - server.del( - '/workflows/:workflow', - middlewares, + server.del('/workflows/:workflow', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, router.requireCredential('admin'), - router.resolve.idToEntity({param: 'workflow', required: true}) , - controller.remove - // audit.afterRemove('workflow',{ display: 'name', topic: crudTopic }) + router.resolve.idToEntity({param: 'workflow', required: true}), + restify.plugins.conditionalHandler([ + { version: '1.0.0', handler: remove }, + { version: '2.9.0', handler: crudv2.remove } + ]), + audit.afterRemove('workflow', { display: 'name', topic: CRUD_TOPIC }) ) - server.put( - '/workflows/:workflow', - middlewares, + server.put('/workflows/:workflow', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, router.requireCredential('admin'), - router.resolve.idToEntity({param: 'workflow', required: true}) , - controller.replace - // audit.afterReplace('workflow',{ display: 'name', topic: crudTopic }) + router.resolve.idToEntity({ param: 'workflow', required: true }) , + restify.plugins.conditionalHandler([ + { version: '2.9.0', handler: crudv2.replace } + ]), + audit.afterReplace('workflow', { display: 'name', topic: CRUD_TOPIC }) ) } -const controller = { - /** - * - * @method GET - * - */ - get (req, res, next) { - res.send(200, req.workflow) - next() - }, - /** - * - * @method GET - * - */ - fetch (req, res, next) { - const customer = req.customer - const input = req.query - - const filter = dbFilter(input,{ /** default **/ }) - filter.where.customer_id = customer.id - if (!ACL.hasAccessLevel(req.user.credential,'admin')) { - // find what this user can access - filter.where.acl = req.user.email - } - - Workflow.fetchBy(filter, (err, workflows) => { - if (err) return res.send(500, err) - res.send(200, workflows) - next() - }) - }, - /** - * - * @method DELETE - * - */ - remove (req, res, next) { - var workflow = req.workflow - workflow.remove(err => { - if (err) return res.send(500,err) - - unlinkWorkflowTasks(req) - res.send(200) - }) - }, - /** - * - * @method PUT - * - */ - replace (req, res, next) { - replaceWorkflow(req, (err, workflow) => { - if (err) { - return res.send(err.statusCode || 500, err) - } - - assignWorkflowAclToTasks(workflow, (err) => { - if (err) { return res.send(err.statusCode || 500, err) } - - res.send(200, workflow) - }) - }) - }, - //async acl (req, res, next) { - // try { - // const workflow = req.workflow - // // validate acl - // workflow.acl = req.body - // assignWorkflowAclToTasks(workflow, (err) => { - // if (err) { - // return res.send(err.statusCode || 500, err) - // } - // res.send(200, workflow) - // }) - // } catch (err) { - // } - //}, - /** - * - * @method POST - * - */ - create (req, res, next) { - createWorkflow(req, (err, workflow) => { - if (err) { return res.send(err.statusCode || 500, err) } - - assignTasksToWorkflow(req, (err) => { - if (err) { return res.send(err.statusCode || 500, err) } - - return res.send(200, workflow) - }) - }) +/** + * + * @method GET + * + */ +const fetch = (req, res, next) => { + const { customer, input = {} } = req + + const filter = dbFilter(input, { /** default **/ }) + filter.where.customer_id = customer.id + if (!ACL.hasAccessLevel(req.user.credential,'admin')) { + // find what this user can access + filter.where.acl = req.user.email } -} - -const createWorkflow = (req, next) => { - const customer = req.customer - const body = req.body - const graph = graphlib.json.read(body.graph) - - validateGraph(graph, (err) => { - if (err) { - err.statusCode = 400 - return next(err) - } - var workflow = new Workflow( - Object.assign({}, body, { - _type: 'Workflow', - customer: customer.id, - customer_id: customer.id, - user: req.user.id, - user_id: req.user.id - }) - ) - - workflow.save(err => { - if (err) { logger.error('%o', err) } - req.workflow = workflow - createTags(body.tags, customer) - return next(err, workflow) - }) + App.Models.Workflow.Workflow.fetchBy(filter, (err, workflows) => { + if (err) return res.send(500, err) + res.send(200, workflows) + next() }) } -/** - * - * take all the tasks on this workflow and assign to it - * - */ -const assignTasksToWorkflow = (req, next) => { +const remove = (req, res, next) => { const workflow = req.workflow - const graph = graphlib.json.read(workflow.graph) + workflow.remove(err => { + if (err) return res.send(500,err) - graph.nodes().forEach(id => { - let node = graph.node(id) - if (!/Event/.test(node._type)) { - App.task.assignTaskToWorkflow(id, workflow) - } + unlinkWorkflowTasks(req) + res.send(200) }) - - next() } const unlinkWorkflowTasks = (req) => { @@ -219,80 +118,3 @@ const unlinkWorkflowTasks = (req) => { } }) } - -const replaceWorkflow = (req, next) => { - const workflow = req.workflow - const body = req.body - const newgraph = graphlib.json.read(body.graph) - const oldgraph = graphlib.json.read(workflow.graph) - - validateGraph(newgraph, (err) => { - if (err) { - err.statusCode = 400 - return next(err) - } - - workflow.set(body) - workflow.save(err => { - if (err) { - logger.error('%s, %s, errors: %o',err.name, err.message, err.errors) - if (err.name === 'ValidationError') { - err.statusCode = 400 - } - return next(err) - } - - updateWorkflowGraphTasksDiferences(oldgraph, newgraph, workflow) - createTags(body.tags, req.customer) - - next(err, workflow) - }) - }) -} - -const createTags = (tags, customer) => { - if (tags && Array.isArray(tags)) { - Tag.create(tags, customer) - } -} - -const validateGraph = (graph, next) => { - if (graph.nodes().length === 0 || graph.edges().length === 0) { - return next(new Error('invalid graph definition')) - } - return next() -} - -const updateWorkflowGraphTasksDiferences = (oldgraph, newgraph, workflow) => { - newgraph.nodes().forEach(id => { - let node = oldgraph.node(id) - if (!node) { // is new - node = newgraph.node(id) - if (!/Event/.test(node._type)) { - App.task.assignTaskToWorkflow(id, workflow) - } - } - }) - oldgraph.nodes().forEach(id => { - let node = newgraph.node(id) - if (!node) { // is no more in the workflow - node = oldgraph.node(id) - if (!/Event/.test(node._type)) { - App.task.unlinkTaskFromWorkflow(id) - } - } - }) -} - -const assignWorkflowAclToTasks = (workflow, next) => { - const graph = graphlib.json.read(workflow.graph) - - graph.nodes().forEach(id => { - let node = graph.node(id) - if (!/Event/.test(node._type)) { - App.task.assignWorkflowAclToTask(id, workflow) - } - }) - - next() -} diff --git a/core/controllers/workflow/crudv2.js b/core/controllers/workflow/crudv2.js new file mode 100644 index 00000000..5a859d2e --- /dev/null +++ b/core/controllers/workflow/crudv2.js @@ -0,0 +1,294 @@ + +const ObjectId = require('mongoose').Types.ObjectId +const isMongoId = require('validator/lib/isMongoId') +const graphlib = require('graphlib') +const App = require('../../app') +const TopicsConstants = require('../../constants/topics') +const logger = require('../../lib/logger')('controller:workflow') +const audit = require('../../lib/audit') +const router = require('../../router') +// const audit = require('../../lib/audit') +const Workflow = require('../../entity/workflow').Workflow +const Task = require('../../entity/task').Entity +const Tag = require('../../entity/tag').Entity + +const ACL = require('../../lib/acl') +const dbFilter = require('../../lib/db-filter') +const AsyncController = require('../../lib/async-controller') +const { ClientError, ServerError } = require('../../lib/error-handler') + +/** + * + * @method POST + * + */ +const create = async (req, res, next) => { + const { customer, user, body } = req + + if (body.graph.nodes.length === 0) { + throw new ClientError('invalid graph definition') + } + + const workflow = new Workflow( + Object.assign({}, body, { + _type: 'Workflow', + customer: customer.id, + customer_id: customer.id, + user: user.id, + user_id: user.id, + version: 2 + }) + ) + + const payload = { workflow, customer, user, body } + const { tasks, graph } = await createTasks(payload) + + // updated grph with created tasks ids + workflow.graph = graph + await workflow.save() + + //createTags(req) + req.workflow = workflow + return workflow +} + +/** + * + * @method PUT + * + */ +const replace = async (req, res, next) => { + const { workflow, customer, user, body } = req + const { graph, tasks, start_task_id } = req.body + + if (body.graph.nodes.length === 0) { + throw new ClientError('invalid graph definition') + } + + const oldgraphlib = graphlib.json.read(workflow.graph) + + const checkRemovedNodes = async () => { + const newgraph = graphlib.json.read(graph) + // update removed nodes + const oldNodes = oldgraphlib.nodes() + for (let id of oldNodes) { + const node = newgraph.node(id) + if (!node) { // it is not in the workflow no more + const oldNode = oldgraphlib.node(id) + if (workflow.version === 2) { + await App.task.destroy(oldNode.id) + } else { + await App.task.unlinkTaskFromWorkflow(oldNode.id) + } + } + } + } + + const checkCreatedNodes = async (graph) => { + // add new nodes + for (let node of graph.nodes) { + if (!oldgraphlib.node(node.v)) { // is not in the workflow + + const props = tasks.find(task => { + return (task.id === node.value.id) + }) + + const model = await App.task.factory( + Object.assign({}, props, { + customer_id: customer._id, + customer: customer, + user: user, + user_id: user.id, + workflow_id: workflow._id, + workflow: workflow, + id: undefined + }) + ) + + if (props.id === start_task_id) { + workflow.start_task = model._id + workflow.start_task_id = model._id + } + + // update node and edges + const newid = model._id.toString() + + // first: update the edges. + for (let edge of graph.edges) { + const eventName = edge.value + + if (edge.v === node.v) { + edge.v = newid + + await createTaskEvent({ + customer_id: customer._id, + name: eventName, + task_id: model._id + }) + } + + if (edge.w === node.v) { + edge.w = newid + + if (isMongoId(edge.v)) { + const task_id = ObjectId(edge.v) + await createTaskEvent({ + customer_id: customer._id, + name: eventName, + task_id + }) + } + } + } + + // second: update the node. + node.value.id = node.v = newid + } + } + } + + await checkRemovedNodes() + await checkCreatedNodes(graph) + + workflow.set(body) + await workflow.save() + + createTags(req) + return workflow +} + +/** + * + * @method DELETE + * + */ +const remove = async (req) => { + const { workflow, body } = req + + await Promise.all([ + workflow.remove(), + App.Models.Job.Workflow.deleteMany({ workflow_id: workflow._id.toString() }), + App.scheduler.unscheduleWorkflow(workflow), + removeWorkflowTasks(workflow, body?.keepTasks) + ]) + + return +} + +module.exports = { + create: AsyncController(create), + remove: AsyncController(remove), + replace: AsyncController(replace) +} + +const createTags = ({ body, customer }) => { + const tags = body.tags + if (tags && Array.isArray(tags)) { + Tag.create(tags, customer) + } +} + +const validateGraph = (graph, next) => { + if (graph.nodes().length === 0) { + return next(new Error('invalid graph definition')) + } + return next() +} + +const createTasks = async ({ workflow, customer, user, body }) => { + const { tasks, graph } = body + const models = [] + + for (let node of graph.nodes) { + const props = tasks.find(task => task.id === node.value.id) + const model = await App.task.factory( + Object.assign({}, props, { + customer_id: customer._id, + customer: customer, + user: user, + user_id: user.id, + workflow_id: workflow._id, + workflow: workflow, + id: undefined + }) + ) + + if (props.id === body.start_task_id) { + workflow.start_task = model._id + workflow.start_task_id = model._id + } + + // update node and edges + const id = model._id.toString() + + // first: update the edges. + for (let edge of graph.edges) { + const eventName = edge.value + + if (edge.v === node.v) { + edge.v = id + + await createTaskEvent({ + customer_id: customer._id, + name: eventName, + task_id: model._id + }) + } + + if (edge.w === node.v) { + edge.w = id + + if (isMongoId(edge.v)) { + const task_id = ObjectId(edge.v) + await createTaskEvent({ + customer_id: customer._id, + name: eventName, + task_id + }) + } + } + } + + // second: update the node. + node.value.id = node.v = id + models.push( model ) + } + + return { tasks: models, graph } +} + +const createTaskEvent = async ({ customer_id, name, task_id }) => { + let tEvent = await App.Models.Event.TaskEvent.findOne({ + name, + customer_id, + emitter_id: task_id + }) + if (!tEvent) { + tEvent = new App.Models.Event.TaskEvent({ + name, + customer: customer_id, + customer_id, + emitter: task_id, + emitter_id: task_id + }) + await tEvent.save() + } + return tEvent +} + +const removeWorkflowTasks = (workflow, keepTasks = false) => { + const graph = workflow.graph + const promises = [] + + for (let node of graph.nodes) { + const value = node.value + + if (keepTasks === true) { + promises.push( App.task.unlinkTaskFromWorkflow(value.id) ) + } else { + promises.push( App.task.destroy(value.id) ) + } + } + + return Promise.all(promises) +} diff --git a/core/controllers/workflow/index.js b/core/controllers/workflow/index.js index b93d30d5..c0f41079 100644 --- a/core/controllers/workflow/index.js +++ b/core/controllers/workflow/index.js @@ -5,9 +5,11 @@ const acl = require('./acl') const triggers = require('./triggers') const integrations = require('./integrations') const scheduler = require('./scheduler') +const recipe = require('./recipe') module.exports = (server) => { job(server) + recipe(server) acl(server) crud(server) graph(server) diff --git a/core/controllers/workflow/job.js b/core/controllers/workflow/job.js index c5492ff2..217b3e61 100644 --- a/core/controllers/workflow/job.js +++ b/core/controllers/workflow/job.js @@ -10,11 +10,6 @@ const { ClientError } = require('../../lib/error-handler') const ACL = require('../../lib/acl') module.exports = (server) => { - const middlewares = [ - server.auth.bearerMiddleware, - router.resolve.customerNameToEntity({ required: true }), - router.ensureCustomer - ] const verifyStartingTask = async (req, res, next) => { try { @@ -71,7 +66,9 @@ module.exports = (server) => { ) server.del('/workflows/:workflow/job', - middlewares, + server.auth.bearerMiddleware, + router.resolve.customerNameToEntity({ required: true }), + router.ensureCustomer, router.requireCredential('admin'), router.resolve.idToEntity({ param: 'workflow', required: true }), router.ensureCustomerBelongs('workflow'), diff --git a/core/controllers/workflow/recipe.js b/core/controllers/workflow/recipe.js new file mode 100644 index 00000000..16f43c46 --- /dev/null +++ b/core/controllers/workflow/recipe.js @@ -0,0 +1,67 @@ +const App = require('../../app') +const logger = require('../../lib/logger')('controller:workflow:recipe'); +const router = require('../../router'); + +module.exports = (server) => { + server.get( + '/workflow/:workflow/export', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, + router.requireCredential('admin'), + router.resolve.idToEntity({ param: 'workflow', required: true }), + router.ensureAllowed({ entity: { name: 'workflow' } }), + _export + ) + + server.post( + '/workflow/import', + server.auth.bearerMiddleware, + router.resolve.customerSessionToEntity(), + router.ensureCustomer, + router.requireCredential('admin'), + _import + ) +} + +/** + * @summary export workflow recipe + */ +const _export = async (req, res, next) => { + const workflow = req.workflow + + const graph = workflow.graph + + const expGraph = {} + expGraph.options = graph.options + expGraph.nodes = [] + expGraph.edges = [] + + const exportedTasks = [] + for (let node of graph.nodes) { + const taskId = node.value.id + if (/Event/.test(node.value._type) !== true) { + const task = await App.Models.Task.Task.findById(taskId) + const recipe = await taskRecipe(task) + exportedTasks.push(recipe) + } + } + + return res.send(200, exportedTasks) +} + +const taskRecipe = (task) => { + return new Promise((resolve, reject) => { + App.task.getRecipe(task, (err, data) => { + if (err) reject(err) + else resolve(data) + }) + }) +} + +/** + * @summary import workflow recipe + */ +const _import = (req, res, next) => { + res.send(200) +} diff --git a/core/entity/event/index.js b/core/entity/event/index.js index bc52eba6..04ad8fa2 100644 --- a/core/entity/event/index.js +++ b/core/entity/event/index.js @@ -1,20 +1,19 @@ -'use strict'; -const mongodb = require('../../lib/mongodb').db; +const mongodb = require('../../lib/mongodb').db -var BaseSchema = require('./schema'); -var TaskEventSchema = require('./task'); -var MonitorEventSchema = require('./monitor'); -var WebhookEventSchema = require('./webhook'); +const BaseSchema = require('./schema') +const TaskEventSchema = require('./task') +const MonitorEventSchema = require('./monitor') +const WebhookEventSchema = require('./webhook') // default Event does not have a default Emitter -var Event = mongodb.model('Event', new BaseSchema()); -var TaskEvent = Event.discriminator('TaskEvent', TaskEventSchema); -var MonitorEvent = Event.discriminator('MonitorEvent', MonitorEventSchema); -var WebhookEvent = Event.discriminator('WebhookEvent', WebhookEventSchema); +const Event = mongodb.model('Event', new BaseSchema()) +const TaskEvent = Event.discriminator('TaskEvent', TaskEventSchema) +const MonitorEvent = Event.discriminator('MonitorEvent', MonitorEventSchema) +const WebhookEvent = Event.discriminator('WebhookEvent', WebhookEventSchema) -Event.ensureIndexes(); -exports.Event = Event; -exports.TaskEvent = TaskEvent; -exports.MonitorEvent = MonitorEvent; -exports.WebhookEvent = WebhookEvent; +Event.ensureIndexes() +exports.Event = Event +exports.TaskEvent = TaskEvent +exports.MonitorEvent = MonitorEvent +exports.WebhookEvent = WebhookEvent diff --git a/core/entity/workflow/schema.js b/core/entity/workflow/schema.js index 5ed90314..91a282cf 100644 --- a/core/entity/workflow/schema.js +++ b/core/entity/workflow/schema.js @@ -57,6 +57,7 @@ function WorkflowSchema (props) { }, autoremove_completed_jobs: { type: Boolean }, autoremove_completed_jobs_limit: { type: Number, 'default': 5 }, + version: { type: Number } } BaseSchema.call(this, Object.assign({}, properties, props), specs) diff --git a/core/lib/async-controller.js b/core/lib/async-controller.js new file mode 100644 index 00000000..8f9a02c1 --- /dev/null +++ b/core/lib/async-controller.js @@ -0,0 +1,42 @@ +const App = require('../app') + +module.exports = (fn, options = {}) => { + + const controller = async (req, res, next) => { + try { + const result = await fn(req, res) + const body = (result||'ok') + res.send(body) + next() + } catch (err) { + if (err.name === 'ValidationError') { + const clientErr = new ClientError('Invalid Payload') + clientErr.errors = err.errors + res.sendError(clientErr) + } else { + res.sendError(err) + } + } + } + + //const transactionalController = async (req, res, next) => { + // const db = App.db + // const session = await db.startSession() + // req.db_session = session + // try { + // session.startTransaction() + // const result = await fn(req, res) + // await session.commitTransaction() + // const body = (result||'ok') + // res.send(body) + // next() + // } catch (err) { + // await session.abortTransaction() + // res.sendError(err) + // } + // session.endSession() + //} + + //return (options.withTransaction === true) ? transactionalController : controller + return controller +} diff --git a/core/lib/async-middleware.js b/core/lib/async-middleware.js new file mode 100644 index 00000000..e327163a --- /dev/null +++ b/core/lib/async-middleware.js @@ -0,0 +1,10 @@ +module.exports = (fn) => { + return async (req, res, next) => { + try { + await fn(req, res) + next() + } catch (err) { + res.sendError(err) + } + } +} diff --git a/core/service/events/task-trigger-by.js b/core/service/events/task-trigger-by.js index a19a5116..b8416214 100644 --- a/core/service/events/task-trigger-by.js +++ b/core/service/events/task-trigger-by.js @@ -38,6 +38,11 @@ module.exports = async function (payload) { * @param {Job} job the job that generates the event */ const triggeredTaskByEvent = async ({ event, data, job }) => { + + if (!event||!event._id) { + return + } + // search all task triggered by the event, not included in workflows const tasks = await Task.find({ triggers: event._id, diff --git a/core/service/events/workflow.js b/core/service/events/workflow.js index cd4c477a..152d0da0 100644 --- a/core/service/events/workflow.js +++ b/core/service/events/workflow.js @@ -22,7 +22,7 @@ module.exports = async function (payload) { if (payload.job.workflow_job_id) { // a task belonging to a workflow has finished logger.log('This is a workflow event') - await handleWorkflowEvent(payload) + handleWorkflowEvent(payload) } } @@ -33,7 +33,7 @@ module.exports = async function (payload) { payload.topic === TopicConstants.workflow.execution // a task inside a workflow can trigger tasks outside the workflow ) { // workflow trigger has occur - await triggerWorkflowByEvent(payload) + triggerWorkflowByEvent(payload) } } catch (err) { logger.error(err) @@ -50,10 +50,26 @@ const handleWorkflowEvent = async ({ event, data, job }) => { // search workflow step by generated event const workflow = await Workflow.findById(workflow_id) if (!workflow) { return } - await executeWorkflowStep(workflow, workflow_job_id, event, data, job) + + const execution = ( + workflow?.version === 2 ? + executeWorkflowStepVersion2 : + executeWorkflowStep + ) + + return execution(workflow, workflow_job_id, event, data, job) } +/** + * + * @return {Promise} + * + */ const executeWorkflowStep = async (workflow, workflow_job_id, event, argsValues, job) => { + if (!event || !event._id) { + return + } + const graph = new graphlib.json.read(workflow.graph) const nodes = graph.successors(event._id.toString()) // should return tasks nodes if (!nodes) { return } @@ -88,12 +104,67 @@ const executeWorkflowStep = async (workflow, workflow_job_id, event, argsValues, return Promise.all(promises) } +/** + * + * @return {Promise} + * + */ +const executeWorkflowStepVersion2 = (workflow, workflow_job_id, event, argsValues, job) => { + + if (!event || !event._id) { + return + } + + const graph = new graphlib.json.read(workflow.graph) + const nodeV = event.emitter_id.toString() + + const nodes = graph.successors(nodeV) + if (!nodes || (Array.isArray(nodes) && nodes.length === 0)) { + return + } + + const promises = [] + for (let nodeW of nodes) { + const edgeLabel = graph.edge(nodeV, nodeW) + if (edgeLabel === event.name) { + promises.push( + Task.findById(nodeW) + .then(task => { + if (!task) { + throw new Error(`workflow step ${nodeW} is missing`) + } + + const { user, dynamic_settings } = ifTriggeredByJobSettings(job) + const createPromise = createJob({ + user, + task, + task_arguments_values: argsValues, + dynamic_settings, + workflow, + workflow_job_id, + origin: JobConstants.ORIGIN_WORKFLOW + }) + + createPromise.catch(err => { return err }) + return createPromise + }) + ) + } + } + + return Promise.all(promises) +} + /** * @param {Event} event entity to process * @param {Job} job the job that generates the event * @param {Mixed} data event data, this is the job.output */ const triggerWorkflowByEvent = async ({ event, data, job }) => { + if (!event || !event._id) { + return + } + const workflows = await Workflow.find({ triggers: event._id }) if (workflows.length===0) { return } diff --git a/core/service/job/index.js b/core/service/job/index.js index 87c05550..9543844f 100644 --- a/core/service/job/index.js +++ b/core/service/job/index.js @@ -540,7 +540,7 @@ module.exports = { process.nextTick(() => { RegisterOperation.submit(Constants.UPDATE, TopicsConstants.job.crud, { job, user }) App.scheduler.cancelScheduledTimeoutVerificationJob(job) // async - dispatchFinishedTaskExecutionEvent(job) + dispatchFinishedJobExecutionEvent(job) emitJobFinishedNotification({ job }) }) } catch (err) { @@ -1059,7 +1059,7 @@ const cancelJobNextLifecycle = (job) => { * @return {Promise} * */ -const dispatchFinishedTaskExecutionEvent = async (job) => { +const dispatchFinishedJobExecutionEvent = async (job) => { try { const { task_id, trigger_name } = job let topic @@ -1073,10 +1073,10 @@ const dispatchFinishedTaskExecutionEvent = async (job) => { name: trigger_name }) - if (!event) { - let warn = `no handler defined for event named ${trigger_name} of task ${task_id}` - return logger.error(warn) - } + //if (!event) { + // const msg = `no handler defined for event named ${trigger_name} of task ${task_id}` + // throw new Error(msg) + //} // trigger task execution event within a workflow if (job.workflow_id && job.workflow_job_id) { @@ -1092,7 +1092,9 @@ const dispatchFinishedTaskExecutionEvent = async (job) => { job }) } catch (err) { - if (err) { return logger.error(err) } + if (err) { + return logger.error(err) + } } } diff --git a/core/service/scheduler.js b/core/service/scheduler.js index 85315d1b..20a26bac 100644 --- a/core/service/scheduler.js +++ b/core/service/scheduler.js @@ -182,6 +182,15 @@ Scheduler.prototype = { ] }) }, + // deletes ALL schedules for a given task + unscheduleWorkflow (workflow) { + return this.agenda.cancel({ + $and: [ + { name: SchedulerConstants.AGENDA_WORKFLOW }, + { 'data.workflow_id': workflow._id }, + ] + }) + }, /** * @param {Job} job * @return {Promise} diff --git a/core/service/task.js b/core/service/task.js index a4d6b99e..cd492032 100644 --- a/core/service/task.js +++ b/core/service/task.js @@ -1,4 +1,3 @@ -'use strict' const App = require('../app') @@ -8,9 +7,7 @@ const logger = require('../lib/logger')('service:task') const Tag = require('../entity/tag').Entity const Host = require('../entity/host').Entity const Task = require('../entity/task').Entity -const TaskFactory = require('../entity/task').Factory const TaskEvent = require('../entity/event').TaskEvent -//const Script = require('../entity/file').Script const Job = require('../entity/job').Job const Constants = require('../constants') const LifecycleConstants = require('../constants/lifecycle') @@ -21,8 +18,40 @@ const TaskTemplate = require('../entity/task/template') // var filter = require('../router/param-filter'); const FetchBy = require('../lib/fetch-by') +const ErrorHandler = require('../lib/error-handler') +const { ClientError, ServerError } = ErrorHandler module.exports = { + /** + * @return {Promise} + */ + factory (props) { + validateProperties(props) + + const task = App.Models.Task.Factory.create(props) + const errors = task.validateSync() + if (errors) { + const err = new ClientError('TaskValidationError') + err.errors = errors + throw err + } + + return task.save() + }, + async destroy (taskId) { + const task = await App.Models.Task.Task.findById(taskId) + if (!task) { + logger.error(`Task not found ${taskId}`) + return + } + + return Promise.all([ + task.remove(), + App.scheduler.unscheduleTask(task), + App.Models.Event.Event.deleteMany({ emitter_id: task._id }), + App.Models.Job.Job.deleteMany({ task_id: task._id.toString() }) + ]) + }, /** * @summary Remove task * @param {Object} options @@ -180,8 +209,7 @@ module.exports = { logger.log('creating task') logger.data(input) - const customer = input.customer - const task = TaskFactory.create(input) + const task = App.Models.Task.Factory.create(input) let errors = task.validateSync() if (errors) { @@ -193,6 +221,7 @@ module.exports = { await task.save() + const customer = input.customer createTags(input.tags, customer) createTaskEvents(task, customer) @@ -443,6 +472,38 @@ module.exports = { } } +const validateProperties = (input) => { + const errors = new ErrorHandler() + + if (!input.name) { errors.required('name', input.name) } + if (!input.type) { errors.required('type', input.type) } + + if ( + input.type === TaskConstants.TYPE_SCRIPT || + input.type === TaskConstants.TYPE_SCRAPER + ) { + if (!input.host_id) { errors.required('host', req.host) } + } + + if (input.type === TaskConstants.TYPE_SCRIPT) { + if (!input.script_id) { + errors.required('script', input.script_id) + } + if (!input.script_runas) { + errors.required('script_runas', input.script_runas) + } + } + + if (input.type === TaskConstants.TYPE_APPROVAL) { } + if (input.type === TaskConstants.TYPE_SCRAPER) { } + if (input.type === TaskConstants.TYPE_DUMMY) { } + if (input.type === TaskConstants.TYPE_NOTIFICATION) { } + + if (errors.hasErrors()) { + throw new ClientError(errors) + } +} + // not yet const sendTaskUpdatedEventNotification = (task) => { return diff --git a/misc/playground/password-hash/package-lock.json b/misc/playground/password-hash/package-lock.json new file mode 100644 index 00000000..1a0fd49a --- /dev/null +++ b/misc/playground/password-hash/package-lock.json @@ -0,0 +1,448 @@ +{ + "name": "password-hash", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node-fetch": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.3.tgz", + "integrity": "sha512-BXSmNTLLDHT0UjQDg5E23x+0n/hPDjySqc0ELE4NpCa2wE5qmmaEWFRP/+v8pfuocchR9l5vFLbSB7CPE2ahvQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/misc/playground/password-hash/package.json b/misc/playground/password-hash/package.json index fba93190..c6ec8a6b 100644 --- a/misc/playground/password-hash/package.json +++ b/misc/playground/password-hash/package.json @@ -10,6 +10,6 @@ "author": "facugon", "license": "ISC", "dependencies": { - "bcrypt": "^0.8.3" + "bcrypt": "~5.0.1" } }