skyline/server/app.js

375 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const logger = require('log4js');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const nunjucks = require('nunjucks');
const httpProxy = require('http-proxy');
const _ = require('underscore');
const crypto = require('crypto');
const helmet = require('helmet');
const uuid = require('uuid');
//session
const session = require('express-session');
// const Redis = require('ioredis');
// const RedisStore = require('connect-redis')(session);
var FileStore = require('session-file-store')(session);
//constants
const Consts = require('../constants');
const context = require('./lib/Context').getCurrentContext();
//login state filter
const loginStateFilter = require('./lib/LoginStateFilter')();
const DEFAULT_SESSION_TIMEOUT_SECOND = 60 * 60;
//性能监控工具
// const easyMonitor = require('easy-monitor');
// const server = function (serverApp) {
logger.configure({
replaceConsole: true,
levels: {
"[all]": "WARN" //ERROR
},
appenders: [
{ type: "console" }
]
});
// easyMonitor('icm');
//设置项目部署根目录
let app = express();
// var compress = require('compression');
// app.use(compress());
// app.use(Consts.ROOT_URL, app);
//非/icm开头的路由重定向到/icm/
// app.use('/', function (req, res) {
// return res.redirect(Consts.ROOT_URL + '/');
// });
//根目录
global.DIR_NAME = __dirname;
global.context = context;
let __DEV__ = process.env.NODE_ENV === 'development';
var ENV = 'dev';
if (__DEV__) {
ENV = 'dev';
} else {
ENV = 'prod';
}
app.locals.ENV = ENV;
context.setEnv(ENV);
logger.configure(context.getResource('log4js.json'));
process.on('uncaughtException', function (err) {
logger.getLogger("Global").error(err.stack || err);
setTimeout(function () {
process.exit(1);
}, 100);
});
let VIEW_PATH = __DEV__ ? Consts.DEV_VIEWS : Consts.DIST_VIEWS;
//sever only
// VIEW_PATH = path.join(Consts.SERVER, 'views');
// if (__DEV__) {
app.disable('view cache');
// }
app.enable('trust proxy');
app.set('trust proxy', 1); // trust first proxy
app.disable('x-powered-by');
// view engine setup
app.set('views', VIEW_PATH);
// app.set('view engine', 'jade');
// app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
let nunjucksEnv = nunjucks.configure(VIEW_PATH, {
autoescape: true,
express: app,
watch: __DEV__,
noCache: __DEV__
});
// json formatting
nunjucksEnv.addFilter('safeJson', function (obj) {
});
// app.set('view engine', 'nunjucks');
// uncomment after placing your favicon in /public
app.use(favicon(path.join(__dirname, '../static', 'favicon.ico')));
// app.use(morganLogger('dev'));
let httpLogger = logger.connectLogger(logger.getLogger("http"), {
level: 'auto',
format: (req, res, format) => {
return format(`:remote-addr - ":method :url HTTP/:http-version" :status :content-length ":referrer" ":user-agent"`);
}
});
app.use(httpLogger);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
let sessionConf = {
secret: 'chyingp', // 用来对session id相关的cookie进行签名
store: new FileStore(), // 本地存储session文本文件也可以选择其他store比如redis的
saveUninitialized: false, // 是否自动保存未初始化的会话建议false
resave: false, // 是否每次都重新保存会话建议false
rolling: true,
cookie: {
maxAge: DEFAULT_SESSION_TIMEOUT_SECOND * 1000
}
};
const serviceObj = context.getResource('serviceAddr.json');
// let redisClient = new Redis(serviceObj.redis);
// sessionConf.store = new RedisStore({
// client: redisClient
// });
// global.redisClient = redisClient;
app.use(session(sessionConf));
//临时中间件,迎合前端使用
app.use(/^.+\.(jsp|html)$/, function (req, res, next) {
req.method = "GET"; //GET必须大写no reason
next();
});
app.use(loginStateFilter);
/**
* initialize proxy
*/
var proxyServer = httpProxy.createProxyServer();
context.setResource('proxy', proxyServer);
proxyServer.on("error", function (e) {
logger.getLogger("proxy").error(e.message);
});
app.use('/', function (req, res, next) {
let sessionTimeout = DEFAULT_SESSION_TIMEOUT_SECOND * 1000;
let now = new Date();
if ('sessionTimeout' in req.session) {
try {
sessionTimeout = parseInt(req.session['sessionTimeout']) * 60 * 1000;
} catch (e) {
console.error(e);
}
}
if (!('lastModified' in req.session)) { // session中没有lastModified则认为是一个新的session
req.session['lastModified'] = now.getTime();
req.session.save(() => {
next();
});
} else {
if (req.session['lastModified'] + sessionTimeout < now.getTime()) {
req.session.regenerate(function () {
// res.status(401); // 超时,需要重新登录
// res.end();
res.redirect('./login.html');
});
} else {
req.session['lastModified'] = now.getTime();
req.session.save(() => {
next();
});
}
}
});
// csrf filter
app.use('/', function (req, res, next) {
let referer = req.headers.referer;
// req.session.token
let loginUrl = req.baseUrl ? (req.baseUrl + '/login.html#') : '/login.html#';
if (typeof (referer) === "undefined") {
next();
} else {
if (referer !== null && referer !== "") {
let protocol = req.protocol;
if (protocol == "wss" || protocol == "ws") {
next();
}
let host = req.hostname;
let originalUrl = protocol + "://" + host;
if (referer.indexOf(originalUrl) === 0) {
next();
} else {
if (referer.indexOf(`http://${host}`) === 0) { // 如果是从http跳转过来的则放行
next();
} else if (req.originalUrl.indexOf('://login.html')) { // 如果是要访问登录页面,则放行
next();
} else {
res.status(406);
res.redirect(loginUrl);
return;
}
}
} else {
next();
}
}
});
app.use(helmet());
//内容安全政策CSP
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'none'"],
scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'"],
styleSrc: ["'self' 'unsafe-inline'"],
imgSrc: ["'self' data:"],
connectSrc: ["https: http: wss: ws: data: 'unsafe-inline' 'unsafe-eval'"],
fontSrc: ["'self' data:"],
objectSrc: ["'self'"],
mediaSrc: ["'self'"],
frameSrc: ["http: https:"]
}
}));
// XSS filter
//app.use(helmet.xssFilter());
// frame options
app.use(helmet.frameguard({ action: 'sameorigin' }));
// hide X-Powered-By
app.use(helmet.hidePoweredBy({ setTo: 'skyline' }));
// 安全测试添加响应头设置安全字段
app.use('/', function (req, res, next) {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
//会话续期
app.use('/', function (req, res, next) {
let userName = req.session.userId || "";
let pid = req.session.pid === undefined ? '' : req.session.pid;
let roleType = req.session.roleType === undefined ? '' : req.session.roleType;
let roleId = req.session.roleId === undefined ? '' : req.session.roleId;
if (req.session.token &&
!(req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) &&
!req.url.startsWith('/node-api/login') &&
!req.url.startsWith('/node-api/keystone/login') &&
!req.url.startsWith('/node-api/keystone/getVerifyCode') &&
!req.url.startsWith('/node-api/keystone/getVerifyCodeConfig') &&
!req.url.startsWith('/node-api/keystone/downloadObject') &&
!req.url.startsWith('/node-api/keystone/logo') &&
!req.url.startsWith('/node-api/keystone/password') &&
!req.url.startsWith('/node-api/keystone/config-before-auth') &&
!req.url.startsWith('/node-api/downloadReport') &&
!req.url.startsWith('/node-api/fake-s3') &&
(req.url.indexOf('/api/') > -1 ||
req.url.indexOf('/custom-api/') > -1 ||
req.url.indexOf('/node-api/') > -1 ||
req.url.indexOf('/storage-api/') > -1
)
) {
let sid = crypto.createHash('sha256').update(userName + pid + roleType + roleId, 'latin1').digest('base64');
if (sid !== req.headers.sid) {
res.status(403);
res.json({ error: 'sid not match.' });
return;
}
}
if (req.method === "OPTIONS" || req.method === "HEAD") {
if (!req.url.startsWith('/s3-api/rgw')) {
// 对s3接口调用单独放开head方法aws sdk业务需要
res.status(403);
res.end();
return;
}
}
if (!req.headers.polling && (req.session.token || req.session.ptoken)) {
// req.session._garbage = Date.now();
// req.session.touch();
}
next();
});
// CSRF防护
app.use('/', function(req, res, next) {
// 设置x-csrf-token值后面用于比较
if (!req.session.hasOwnProperty('csrfToken')) {
req.session['csrfToken'] = uuid.v1();
req.session.save(() => {
next();
});
} else {
next();
}
});
app.use('/', function(req, res, next) {
// 检查POST、DELETE、PUT、PATCH方法的csrfToken
if (['POST', 'DELETE', 'PUT', 'PATCH'].includes(req.method.toUpperCase())) {
if (!req.url.startsWith('/node-api/') && !req.url.startsWith('/s3-api/') && !req.url.startsWith('/storage-api/')) {
const csrfToken = req.headers['x-csrf-token'];
if (csrfToken !== req.session['csrfToken']) {
res.status(401);
res.end();
} else {
next();
}
} else {
next();
}
} else {
next();
}
});
const replayAttackChecker = require('./routes/security/replayAttackChecker');
// 验证码请求使用image src的方式无法增加header字段只校验api/
app.use([
'/api/',
'/custom-api/',
'/storage-api/'
], replayAttackChecker);
let defaultRouter = express.Router();
defaultRouter.get('/', function(req, res) {
let indexUrl = req.baseUrl ? (req.baseUrl + '/index.html') : '/index.html';
res.redirect(indexUrl);
});
app.use('/', defaultRouter);
//路由挂载
var routerFactory = require('./RouterFactory');
routerFactory.mount(context.getResource('routes.json'), app, context);
//未匹配的路由重定向到首页
// app.use('/', function (req, res) {
// let indexUrl = req.baseUrl ? (req.baseUrl + '/index.html') : '/index.html';
// res.redirect(indexUrl);
// return '';
// });
// // catch 404 and forward to error handler
// app.use(function (req, res, next) {
// var err = new Error('Not Found');
// err.status = 404;
// next(err);
// });
// error handler
// app.use(function (err, req, res) {
// // 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);
// let status = err.status || 500;
// let result = {
// message: err.message,
// error: {},
// status: err.status || 500,
// ret: false
// };
// if (/\.json$/.test(req.path)) {
// res.status(status).send(result);
// } else {
// res.status(status).render('error', result);
// }
// });
// };
module.exports = app;