Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 101 additions & 74 deletions api/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const http = require('http');
const https = require('https');
const fs = require('fs');
const cluster = require('cluster');
const formidable = require('formidable');
const os = require('os');
Expand Down Expand Up @@ -357,88 +359,113 @@ plugins.connectToAllDatabases().then(function() {

plugins.dispatch("/worker", {common: common});

http.Server((req, res) => {
const params = {
qstring: {},
res: res,
req: req
const serverOptions = {
port: common.config.api.port,
host: common.config.api.host || ''
};

let server;
if (common.config.api.ssl && common.config.api.ssl.enabled) {
const sslOptions = {
key: fs.readFileSync(common.config.api.ssl.key),
cert: fs.readFileSync(common.config.api.ssl.cert)
};
if (common.config.api.ssl.ca) {
sslOptions.ca = fs.readFileSync(common.config.api.ssl.ca);
}
server = https.createServer(sslOptions, handleRequest);
}
else {
server = http.createServer(handleRequest);
}

if (req.method.toLowerCase() === 'post') {
const formidableOptions = {};
if (countlyConfig.api.maxUploadFileSize) {
formidableOptions.maxFileSize = countlyConfig.api.maxUploadFileSize;
}
server.listen(serverOptions.port, serverOptions.host).timeout = common.config.api.timeout || 120000;
}
});

const form = new formidable.IncomingForm(formidableOptions);
if (/crash_symbols\/(add_symbol|upload_symbol)/.test(req.url)) {
req.body = [];
req.on('data', (data) => {
req.body.push(data);
});
/**
* Handle incoming HTTP/HTTPS requests
* @param {http.IncomingMessage} req - The request object
* @param {http.ServerResponse} res - The response object
*/
function handleRequest(req, res) {
const params = {
qstring: {},
res: res,
req: req
};

if (req.method.toLowerCase() === 'post') {
const formidableOptions = {};
if (countlyConfig.api.maxUploadFileSize) {
formidableOptions.maxFileSize = countlyConfig.api.maxUploadFileSize;
}

const form = new formidable.IncomingForm(formidableOptions);
if (/crash_symbols\/(add_symbol|upload_symbol)/.test(req.url)) {
req.body = [];
req.on('data', (data) => {
req.body.push(data);
});
}
else {
req.body = '';
req.on('data', (data) => {
req.body += data;
});
}

let multiFormData = false;
// Check if we have 'multipart/form-data'
if (req.headers['content-type']?.startsWith('multipart/form-data')) {
multiFormData = true;
}

form.parse(req, (err, fields, files) => {
//handle bakcwards compatability with formiddble v1
for (let i in files) {
if (files[i].filepath) {
files[i].path = files[i].filepath;
}
else {
req.body = '';
req.on('data', (data) => {
req.body += data;
});
if (files[i].mimetype) {
files[i].type = files[i].mimetype;
}

let multiFormData = false;
// Check if we have 'multipart/form-data'
if (req.headers['content-type']?.startsWith('multipart/form-data')) {
multiFormData = true;
if (files[i].originalFilename) {
files[i].name = files[i].originalFilename;
}

form.parse(req, (err, fields, files) => {
//handle bakcwards compatability with formiddble v1
for (let i in files) {
if (files[i].filepath) {
files[i].path = files[i].filepath;
}
if (files[i].mimetype) {
files[i].type = files[i].mimetype;
}
if (files[i].originalFilename) {
files[i].name = files[i].originalFilename;
}
}
params.files = files;
if (multiFormData) {
let formDataUrl = [];
for (const i in fields) {
params.qstring[i] = fields[i];
formDataUrl.push(`${i}=${fields[i]}`);
}
params.formDataUrl = formDataUrl.join('&');
}
else {
for (const i in fields) {
params.qstring[i] = fields[i];
}
}
if (!params.apiPath) {
processRequest(params);
}
});
}
else if (req.method.toLowerCase() === 'options') {
const headers = {};
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS";
headers["Access-Control-Allow-Headers"] = "countly-token, Content-Type";
res.writeHead(200, headers);
res.end();
}
//attempt process GET request
else if (req.method.toLowerCase() === 'get') {
processRequest(params);
params.files = files;
if (multiFormData) {
let formDataUrl = [];
for (const i in fields) {
params.qstring[i] = fields[i];
formDataUrl.push(`${i}=${fields[i]}`);
}
params.formDataUrl = formDataUrl.join('&');
}
else {
common.returnMessage(params, 405, "Method not allowed");
for (const i in fields) {
params.qstring[i] = fields[i];
}
}
}).listen(common.config.api.port, common.config.api.host || '').timeout = common.config.api.timeout || 120000;

plugins.loadConfigs(common.db);
if (!params.apiPath) {
processRequest(params);
}
});
}
});
else if (req.method.toLowerCase() === 'options') {
const headers = {};
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS";
headers["Access-Control-Allow-Headers"] = "countly-token, Content-Type";
res.writeHead(200, headers);
res.end();
}
//attempt process GET request
else if (req.method.toLowerCase() === 'get') {
processRequest(params);
}
else {
common.returnMessage(params, 405, "Method not allowed");
}
}
6 changes: 6 additions & 0 deletions api/config.sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ var countlyConfig = {
max_sockets: 1024,
timeout: 120000,
maxUploadFileSize: 200 * 1024 * 1024, // 200MB
ssl: {
enabled: false,
key: "/path/to/ssl/private.key",
cert: "/path/to/ssl/certificate.crt",
ca: "/path/to/ssl/ca_bundle.crt" // Optional: for client certificate verification
}
},
/**
* Path to use for countly directory, empty path if installed at root of website
Expand Down
16 changes: 14 additions & 2 deletions api/configextender.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,26 @@ const OVERRIDES = {

API: {
MAX_SOCKETS: 'max_sockets',
MAX_UPLOAD_FILE_SIZE: 'maxUploadFileSize'
MAX_UPLOAD_FILE_SIZE: 'maxUploadFileSize',
SSL: {
ENABLED: 'enabled',
KEY: 'key',
CERT: 'cert',
CA: 'ca',
},
},

WEB: {
USE_INTERCOM: 'use_intercom',
SECURE_COOKIES: 'secure_cookies',
SESSION_SECRET: 'session_secret',
SESSION_NAME: 'session_name'
SESSION_NAME: 'session_name',
SSL: {
ENABLED: 'enabled',
KEY: 'key',
CERT: 'cert',
CA: 'ca',
},
},

MAIL: {
Expand Down
120 changes: 120 additions & 0 deletions bin/config/nginx.server.internal_ssl.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
server {
listen 80;
listen [::]:80 ipv6only=on;
server_name localhost;

access_log off;

rewrite ^ https://$host$request_uri? permanent;
}

# HTTPS configuration

server {
listen 443;
listen [::]:443 ipv6only=on;
server_name localhost;

access_log off;

ssl on;

# support only known-secure cryptographic protocols
# SSLv3 is broken by POODLE as of October 2014
ssl_protocols TLSv1.2 TLSv1.3;

# make the server choose the best cipher instead of the browser
# Perfect Forward Secrecy(PFS) is frequently compromised without this
ssl_prefer_server_ciphers on;

# support only believed secure ciphersuites using the following priority:
# 1.) prefer PFS enabled ciphers
# 2.) prefer AES128 over AES256 for speed (AES128 has completely adequate security for now)
# 3.) Support DES3 for IE8 support
#
# disable the following ciphersuites completely
# 1.) null ciphers
# 2.) ciphers with low security
# 3.) fixed ECDH cipher (does not allow for PFS)
# 4.) known vulnerable cypers (MD5, RC4, etc)
# 5.) little-used ciphers (Camellia, Seed)
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 +SHA !DES-CBC3-SHA !aNULL !eNULL !LOW !kECDH !DSS !3DES !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

# Cache SSL Sessions for up to 10 minutes
# This improves performance by avoiding the costly session negotiation process where possible
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# allow Nginx to send OCSP results during the connection process
ssl_stapling on;

# Use 2048 bit Diffie-Hellman RSA key parameters
# (otherwise Nginx defaults to 1024 bit, lowering the strength of encryption # when using PFS)
# Generated by OpenSSL with the following command:
# openssl dhparam -outform pem -out /etc/nginx/ssl/dhparam2048.pem 2048
ssl_dhparam /path/to/dhparams.pem;

# Provide path to certificates and keys
ssl_certificate /path/to/certificate-bundle.crt;
ssl_certificate_key /path/to/certificate-key.key;
ssl_trusted_certificate /path/to/chain.pem;

location = /i {
if ($http_content_type = "text/ping") {
return 404;
}
# countly server is running with ssl, so use https here
proxy_pass https://localhost:3001;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
# if countly server is using self-signed certificate, this will disable certificate verification
proxy_ssl_verify off;
}

location ^~ /i/ {
if ($http_content_type = "text/ping") {
return 404;
}
# countly server is running with ssl, so use https here
proxy_pass https://localhost:3001;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
# if countly server is using self-signed certificate, this will disable certificate verification
proxy_ssl_verify off;
}

location = /o {
if ($http_content_type = "text/ping") {
return 404;
}
# countly server is running with ssl, so use https here
proxy_pass https://localhost:3001;
# if countly server is using self-signed certificate, this will disable certificate verification
proxy_ssl_verify off;
}

location ^~ /o/ {
if ($http_content_type = "text/ping") {
return 404;
}
# countly server is running with ssl, so use https here
proxy_pass https://localhost:3001;
# if countly server is using self-signed certificate, this will disable certificate verification
proxy_ssl_verify off;
}

location / {
if ($http_content_type = "text/ping") {
return 404;
}
# countly server is running with ssl, so use https here
proxy_pass https://localhost:6001;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
# if countly server is using self-signed certificate, this will disable certificate verification
proxy_ssl_verify off;
}
}

20 changes: 19 additions & 1 deletion frontend/express/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var versionInfo = require('./version.info'),
COUNTLY_HELPCENTER_LINK = (typeof versionInfo.helpCenterLink === "undefined") ? true : (typeof versionInfo.helpCenterLink === "string") ? versionInfo.helpCenterLink : (typeof versionInfo.helpCenterLink === "boolean") ? versionInfo.helpCenterLink : true,
COUNTLY_FEATUREREQUEST_LINK = (typeof versionInfo.featureRequestLink === "undefined") ? true : (typeof versionInfo.featureRequestLink === "string") ? versionInfo.featureRequestLink : (typeof versionInfo.featureRequestLink === "boolean") ? versionInfo.featureRequestLink : true,
express = require('express'),
https = require('https'),
SkinStore = require('./libs/connect-mongo.js'),
expose = require('./libs/express-expose.js'),
dollarDefender = require('./libs/dollar-defender.js')({
Expand Down Expand Up @@ -1933,5 +1934,22 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_
countlyDb.collection('jobs').createIndex({ name: 1 }, function() {});
countlyDb.collection('long_tasks').createIndex({ manually_create: 1, start: -1 }, function() {});

app.listen(countlyConfig.web.port, countlyConfig.web.host || '');
const serverOptions = {
port: countlyConfig.web.port,
host: countlyConfig.web.host || ''
};

if (countlyConfig.web.ssl && countlyConfig.web.ssl.enabled) {
const sslOptions = {
key: fs.readFileSync(countlyConfig.web.ssl.key),
cert: fs.readFileSync(countlyConfig.web.ssl.cert)
};
if (countlyConfig.web.ssl.ca) {
sslOptions.ca = fs.readFileSync(countlyConfig.web.ssl.ca);
}
https.createServer(sslOptions, app).listen(serverOptions.port, serverOptions.host);
}
else {
app.listen(serverOptions.port, serverOptions.host);
}
});
Loading
Loading