@tiledesk/tiledesk-server
Version:
The Tiledesk server module
682 lines (481 loc) • 23.9 kB
JavaScript
var dotenvPath = undefined;
if (process.env.DOTENV_PATH) {
dotenvPath = process.env.DOTENV_PATH;
console.log("load dotenv form DOTENV_PATH", dotenvPath);
}
if (process.env.LOAD_DOTENV_SUBFOLDER ) {
console.log("load dotenv form LOAD_DOTENV_SUBFOLDER");
dotenvPath = __dirname+'/confenv/.env';
}
require('dotenv').config({ path: dotenvPath});
var express = require('express');
var path = require('path');
// var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var morgan = require('morgan');
var mongoose = require('mongoose');
var passport = require('passport');
require('./middleware/passport')(passport);
var config = require('./config/database');
var cors = require('cors');
var Project = require("./models/project");
var validtoken = require('./middleware/valid-token');
var roleChecker = require('./middleware/has-role');
const MaskData = require("maskdata");
var winston = require('./config/winston');
// DATABASE CONNECTION
// https://bretkikehara.wordpress.com/2013/05/02/nodejs-creating-your-first-global-module/
var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;
if (!databaseUri) { //TODO??
winston.warn('DATABASE_URI not specified, falling back to localhost.');
}
if (process.env.NODE_ENV == 'test') {
databaseUri = config.databasetest;
}
const masked_databaseUri = MaskData.maskPhone(databaseUri, {
maskWith : "*",
unmaskedStartDigits: 15,
unmaskedEndDigits: 5
});
if (process.env.DISABLE_MONGO_PASSWORD_MASK ==true || process.env.DISABLE_MONGO_PASSWORD_MASK == "true") {
winston.info("DatabaseUri: " + databaseUri);
}else {
winston.info("DatabaseUri masked: " + masked_databaseUri);
}
var autoIndex = true;
if (process.env.MONGOOSE_AUTOINDEX) {
autoIndex = process.env.MONGOOSE_AUTOINDEX;
}
winston.info("DB AutoIndex: " + autoIndex);
var connection = mongoose.connect(databaseUri, { "useNewUrlParser": true, "autoIndex": autoIndex }, function(err) {
if (err) {
winston.error('Failed to connect to MongoDB on ' + databaseUri + " ", err);
process.exit(1);
}
winston.info("Mongoose connection done on host: "+mongoose.connection.host + " on port: " + mongoose.connection.port + " with name: "+ mongoose.connection.name)// , mongoose.connection.db);
});
if (process.env.MONGOOSE_DEBUG==="true") {
mongoose.set('debug', true);
}
mongoose.set('useFindAndModify', false); // https://mongoosejs.com/docs/deprecations.html#-findandmodify-
mongoose.set('useCreateIndex', true);
mongoose.set('useUnifiedTopology', false);
// CONNECT REDIS - CHECK IT
const { TdCache } = require('./utils/TdCache');
let tdCache = new TdCache({
host: process.env.CACHE_REDIS_HOST,
port: process.env.CACHE_REDIS_PORT,
password: process.env.CACHE_REDIS_PASSWORD
});
tdCache.connect();
// ROUTES DECLARATION
var troubleshooting = require('./routes/troubleshooting');
var auth = require('./routes/auth');
var authtest = require('./routes/authtest');
var authtestWithRoleCheck = require('./routes/authtestWithRoleCheck');
var lead = require('./routes/lead');
var message = require('./routes/message');
var messagesRootRoute = require('./routes/messagesRoot');
var department = require('./routes/department');
var group = require('./routes/group');
var resthook = require('./routes/subscription');
var tag = require('./routes/tag');
var faq = require('./routes/faq');
var faq_kb = require('./routes/faq_kb');
var project = require('./routes/project');
var project_user = require('./routes/project_user');
var project_users_test = require('./routes/project_user_test');
var request = require('./routes/request');
// var setting = require('./routes/setting');
var users = require('./routes/users');
var usersUtil = require('./routes/users-util');
var publicRequest = require('./routes/public-request');
var userRequest = require('./routes/user-request');
var publicAnalytics = require('./routes/public-analytics');
var pendinginvitation = require('./routes/pending-invitation');
var jwtroute = require('./routes/jwt');
var key = require('./routes/key');
var widgets = require('./routes/widget');
var widgetsLoader = require('./routes/widgetLoader');
var openai = require('./routes/openai');
var llm = require('./routes/llm');
var quotes = require('./routes/quotes');
var integration = require('./routes/integration')
var kbsettings = require('./routes/kbsettings');
var kb = require('./routes/kb');
// var admin = require('./routes/admin');
var faqpub = require('./routes/faqpub');
var labels = require('./routes/labels');
var fetchLabels = require('./middleware/fetchLabels');
var cacheUtil = require("./utils/cacheUtil");
var orgUtil = require("./utils/orgUtil");
var images = require('./routes/images');
var files = require('./routes/files');
var campaigns = require('./routes/campaigns');
var logs = require('./routes/logs');
var requestUtilRoot = require('./routes/requestUtilRoot');
var urls = require('./routes/urls');
var email = require('./routes/email');
var property = require('./routes/property');
var segment = require('./routes/segment');
var webhook = require('./routes/webhook');
var webhooks = require('./routes/webhooks');
var copilot = require('./routes/copilot');
var bootDataLoader = require('./services/bootDataLoader');
var settingDataLoader = require('./services/settingDataLoader');
var schemaMigrationService = require('./services/schemaMigrationService');
var RouterLogger = require('./models/routerLogger');
var cacheEnabler = require("./services/cacheEnabler");
const session = require('express-session');
const RedisStore = require("connect-redis").default
const botEvent = require('./event/botEvent');
require('./services/mongoose-cache-fn')(mongoose);
var subscriptionNotifier = require('./services/subscriptionNotifier');
subscriptionNotifier.start();
var subscriptionNotifierQueued = require('./services/subscriptionNotifierQueued');
var botSubscriptionNotifier = require('./services/BotSubscriptionNotifier');
botSubscriptionNotifier.start(); //queued but disabled
botEvent.listen(); //queued but disabled
var trainingService = require('./services/trainingService');
trainingService.start();
// job_here
var geoService = require('./services/geoService');
// geoService.listen(); //queued
var updateLeadQueued = require('./services/updateLeadQueued');
let JobsManager = require('./jobsManager');
let jobWorkerEnabled = false;
if (process.env.JOB_WORKER_ENABLED=="true" || process.env.JOB_WORKER_ENABLED == true) {
jobWorkerEnabled = true;
}
winston.info("JobsManager jobWorkerEnabled: "+ jobWorkerEnabled);
let jobsManager = new JobsManager(jobWorkerEnabled, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued);
var faqBotHandler = require('./services/faqBotHandler');
faqBotHandler.listen();
var pubModulesManager = require('./pubmodules/pubModulesManager');
pubModulesManager.init({express:express, mongoose:mongoose, passport:passport, databaseUri:databaseUri, routes:{}, jobsManager:jobsManager});
jobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events
let whatsappQueue = require('@tiledesk/tiledesk-whatsapp-jobworker');
winston.info("whatsappQueue");
jobsManager.listenWhatsappQueue(whatsappQueue);
let multiWorkerQueue = require('@tiledesk/tiledesk-multi-worker');
winston.info("multiWorkerQueue from App")
jobsManager.listenMultiWorker(multiWorkerQueue);
var channelManager = require('./channels/channelManager');
channelManager.listen();
var IPFilter = require('./middleware/ipFilter');
// job_here
var BanUserNotifier = require('./services/banUserNotifier');
BanUserNotifier.listen();
const { ChatbotService } = require('./services/chatbotService');
const { QuoteManager } = require('./services/QuoteManager');
let qm = new QuoteManager({ tdCache: tdCache });
qm.start();
var modulesManager = undefined;
try {
modulesManager = require('./services/modulesManager');
modulesManager.init({express:express, mongoose:mongoose, passport:passport, routes: {departmentsRoute: department, projectsRoute: project, widgetsRoute: widgets} });
} catch(err) {
winston.info("ModulesManager not present");
}
//enterprise modules can modify pubmodule
modulesManager.start();
pubModulesManager.start();
settingDataLoader.save();
schemaMigrationService.checkSchemaMigration();
if (process.env.CREATE_INITIAL_DATA !== "false") {
bootDataLoader.create();
}
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set('chatbot_service', new ChatbotService())
app.set('redis_client', tdCache);
app.set('quote_manager', qm);
// TODO DELETE IT IN THE NEXT RELEASE
if (process.env.ENABLE_ALTERNATIVE_CORS_MIDDLEWARE === "true") {
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*"); //qui dice cequens attento
// var request_cors_header = req.headers[""]
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, x-xsrf-token");
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
next();
});
winston.info("Enabled alternative cors middleware");
} else {
winston.info("Used standard cors middleware");
}
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
// app.use(morgan('dev'));
// app.use(morgan('combined'));
// app.use(bodyParser.json());
// https://stackoverflow.com/questions/18710225/node-js-get-raw-request-body-using-express
const JSON_BODY_LIMIT = process.env.JSON_BODY_LIMIT || '500KB';
winston.debug("JSON_BODY_LIMIT : " + JSON_BODY_LIMIT);
app.use(bodyParser.json({limit: JSON_BODY_LIMIT,
verify: function (req, res, buf) {
// var url = req.originalUrl;
// if (url.indexOf('/stripe/')) {
req.rawBody = buf.toString();
winston.debug("bodyParser verify stripe", req.rawBody);
// }
}
}));
app.use(bodyParser.urlencoded({limit: JSON_BODY_LIMIT, extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
//app.use(morgan('dev'));
if (process.env.ENABLE_ACCESSLOG) {
app.use(morgan('combined', { stream: winston.stream }));
}
app.use(passport.initialize());
// After you declare "app"
if (process.env.DISABLE_SESSION_STRATEGY==true || process.env.DISABLE_SESSION_STRATEGY=="true" ) {
winston.info("Express Session disabled");
} else {
// https://www.npmjs.com/package/express-session
let sessionSecret = process.env.SESSION_SECRET || "tiledesk-session-secret";
if (process.env.ENABLE_REDIS_SESSION==true || process.env.ENABLE_REDIS_SESSION=="true" ) {
console.log("Starting redis...") // errors occurs
// Initialize client.
// let redisClient = createClient()
// redisClient.connect().catch(console.error)
let cacheClient = undefined;
if (pubModulesManager.cache) {
cacheClient = pubModulesManager.cache._cache._cache; //_cache._cache to jump directly to redis modules without cacheoose wrapper (don't support await)
}
// winston.info("Express Session cacheClient",cacheClient);
let redisStore = new RedisStore({
client: cacheClient,
prefix: "sessions:",
})
app.use(
session({
store: redisStore,
resave: false, // required: force lightweight session keep alive (touch)
saveUninitialized: false, // recommended: only save session when data exists
secret: sessionSecret
})
)
winston.info("Express Session with Redis enabled with Secret: " + sessionSecret);
} else {
app.use(session({ secret: sessionSecret}));
winston.info("Express Session enabled with Secret: " + sessionSecret);
}
app.use(passport.session());
}
//ATTENTION. If you use AWS Api Gateway you need also to configure the cors policy https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html
app.use(cors());
app.options('*', cors());
// const customRedisRateLimiter = require("./rateLimiter").customRedisRateLimiter;
// app.use(customRedisRateLimiter);
// MIDDLEWARE FOR REQUESTS QUOTE
// app.use('/:projectid/requests', function (req, res, next) {
// console.log("MIDDLEWARE FIRED ---> REQUESTS");
// console.log("(Requests Middleware) method: ", req.method);
// if (req.method === 'POST') {
// let quoteManager = new QuoteManager({ project: mockProject, tdCache: mockTdCache } )
// } else {
// next();
// }
// });
if (process.env.ROUTELOGGER_ENABLED==="true") {
winston.info("RouterLogger enabled ");
app.use(function (req, res, next) {
// winston.error("log ", req);
try {
var projectid = req.projectid;
winston.debug("RouterLogger projectIdSetter projectid:" + projectid);
var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
winston.debug("fullUrl:"+ fullUrl);
winston.debug(" req.get('host'):"+ req.get('host'));
winston.debug("req.get('origin'):" + req.get('origin'));
winston.debug("req.get('referer'):" + req.get('referer'));
var routerLogger = new RouterLogger({
origin: req.get('origin'),
fullurl: fullUrl,
url: req.originalUrl.split("?").shift(),
id_project: projectid,
});
routerLogger.save(function (err, savedRouterLogger) {
if (err) {
winston.error('Error saving RouterLogger ', err)
}
winston.debug("RouterLogger saved "+ savedRouterLogger);
next();
});
}catch(e) {
winston.error('Error saving RouterLogger ', e)
next();
}
});
} else {
winston.info("RouterLogger disabled ");
}
app.get('/', function (req, res) {
res.send('Hello from Tiledesk server. It\'s UP. See the documentation here http://developer.tiledesk.com');
});
var projectIdSetter = function (req, res, next) {
var projectid = req.params.projectid;
winston.debug("projectIdSetter projectid: "+ projectid);
// if (projectid) {
req.projectid = projectid;
// }
next()
}
var projectSetter = function (req, res, next) {
var projectid = req.params.projectid;
winston.debug("projectSetter projectid:" + projectid);
if (projectid) {
if (!mongoose.Types.ObjectId.isValid(projectid)) {
//winston.warn(`Invalid ObjectId: ${projectid}`);
return res.status(400).send({ error: "Invalid project id: " + projectid });
}
let q = Project.findOne({_id: projectid, status: 100});
if (cacheEnabler.project) {
q.cache(cacheUtil.longTTL, "projects:id:"+projectid) //project_cache
winston.debug('project cache enabled');
}
q.exec(function(err, project){
if (err) {
//winston.warn("Problem getting project with id: " + projectid + " req.originalUrl: " + req.originalUrl);
}
winston.debug("projectSetter project:" + project);
if (!project) {
//winston.warn("ProjectSetter project not found with id: " + projectid);
//next();
return res.status(400).send({ error: "Project not found with id: " + projectid })
} else {
req.project = project;
next(); //call next one time for projectSetter function
}
});
}else {
next()
}
}
// app.use('/admin', admin);
//oauth2
// app.get('/dialog/authorize', oauth2.authorization);
// app.post('/dialog/authorize/decision', oauth2.decision);
// app.post('/oauth/token', oauth2.token);
// const ips = ['::1'];
app.use('/troubleshooting', troubleshooting);
app.use('/auth', auth);
app.use('/testauth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtest);
app.use('/widgets', widgetsLoader);
app.use('/w', widgetsLoader);
app.use('/images', images);
app.use('/files', files);
app.use('/urls', urls);
app.use('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], users);
app.use('/users_util', usersUtil);
app.use('/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], logs);
app.use('/requests_util', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], requestUtilRoot);
app.use('/webhook', webhook);
// TODO security issues
if (process.env.DISABLE_TRANSCRIPT_VIEW_PAGE ) {
winston.info(" Transcript view page is disabled");
}else {
app.use('/public/requests', publicRequest);
}
// project internal auth check. TODO check security issues?
app.use('/projects',project);
channelManager.use(app);
if (pubModulesManager) {
pubModulesManager.use(app);
}
if (modulesManager) {
modulesManager.use(app);
}
app.use('/:projectid/', [projectIdSetter, projectSetter, IPFilter.projectIpFilter, IPFilter.projectIpFilterDeny, IPFilter.decodeJwt, IPFilter.projectBanUserFilter]);
app.use('/:projectid/authtestWithRoleCheck', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtestWithRoleCheck);
app.use('/:projectid/project_users_test', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], project_users_test);
app.use('/:projectid/leads', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], lead);
app.use('/:projectid/requests/:request_id/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes(null, ['bot','subscription'])] , message);
app.use('/:projectid/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])] , messagesRootRoute);
// department internal auth check
app.use('/:projectid/departments', department);
channelManager.useUnderProjects(app);
app.use('/:projectid/groups', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], group);
app.use('/:projectid/tags', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], tag);
app.use('/:projectid/subscriptions', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], resthook);
//deprecated
app.use('/:projectid/faq', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);
app.use('/:projectid/intents', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);
//Deprecated??
app.use('/:projectid/faqpub', faqpub);
//deprecated
app.use('/:projectid/faq_kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);
app.use('/:projectid/bots', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);
// app.use('/settings',setting);
app.use('/:projectid/widgets', widgets);
// non mettere ad admin perchà la dashboard richiama il servizio router.get('/:user_id/:project_id') spesso
// TOOD security issues. internal route check
// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);
app.use('/:projectid/project_users', project_user);
// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);
//passport double check this and the next
app.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('guest', ['bot','subscription'])], userRequest);
app.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], request);
app.use('/:projectid/publicanalytics', publicAnalytics);
app.use('/:projectid/keys', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], key);
//TODO deprecated?
app.use('/:projectid/jwt', jwtroute);
app.use('/:projectid/pendinginvitations', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], pendinginvitation);
app.use('/:projectid/labels', [fetchLabels],labels);
app.use('/:projectid/campaigns',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], campaigns);
app.use('/:projectid/emails',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], email);
app.use('/:projectid/properties',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], property);
app.use('/:projectid/segments',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], segment);
app.use('/:projectid/llm', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], llm);
app.use('/:projectid/openai', openai);
app.use('/:projectid/quotes', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], quotes)
app.use('/:projectid/integration', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], integration )
app.use('/:projectid/kbsettings', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], kbsettings);
app.use('/:projectid/kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], kb);
app.use('/:projectid/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], logs);
app.use('/:projectid/webhooks', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], webhooks);
app.use('/:projectid/copilot', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], copilot);
if (pubModulesManager) {
pubModulesManager.useUnderProjects(app);
}
if (modulesManager) {
modulesManager.useUnderProjects(app);
}
// REENABLEIT
// catch 404 and forward to error handler
// app.use(function (req, res, next) {
// var err = new Error('Not Found');
// err.status = 404;
// next(err);
// });
/*
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});*/
// mettere middleware qui per le quote
// error handler
app.use((err, req, res, next) => {
winston.debug("err.name", err.name)
if (err.name === "IpDeniedError") {
winston.debug("IpDeniedError");
return res.status(401).json({ err: "error ip filter" });
}
//emitted by multer when the file is too big
if (err.code === "LIMIT_FILE_SIZE") {
winston.debug("LIMIT_FILE_SIZE");
return res.status(413).json({ err: "Content Too Large", limit_file_size: process.env.MAX_UPLOAD_FILE_SIZE });
}
winston.error("General error:: ", err);
return res.status(500).json({ err: "error" });
});
module.exports = app;