forked from Inspur/skyline
375 lines
12 KiB
JavaScript
375 lines
12 KiB
JavaScript
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;
|