项目初始化提交

This commit is contained in:
gao.fei 2021-04-07 15:01:10 +08:00
parent a902b1c562
commit 63c22e241e
2185 changed files with 435302 additions and 18 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
node_modules/
mock-server/
dev/
sessions/
npm-debug.log
test/
selenium-debug.log

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
build/*.js
config/*.js
src/components/element2/**/*.js
src/common/*.js

43
.eslintrc.js Normal file
View File

@ -0,0 +1,43 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
"indent": ['warn', 2], //warn error
"no-unused-vars": [0, { "vars": "all", "args": "none" }],
// allow paren-less arrow functions
'arrow-parens': 0,
'no-undef': 0,
'spaced-comment': [0, "never"],
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
//'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-debugger': 2,
'semi': [1, "always"],
'func-call-spacing': [0, "never"],
"space-before-function-paren": [0, "always"],
"key-spacing": ["warn", { "afterColon": true }],
"quotes": [0, "single"],
"eqeqeq": ["warn", "always"],
"space-in-parens": [0, "never"],
"space-infix-ops": [0, {"int32Hint": false}],
"yoda": [0, "never", { "exceptRange": true }],
"handle-callback-err": [0, "error"],
"no-extra-bind": [0, "error"],
"no-eval": 0,
"no-useless-escape": 0,
// allow async-await
'generator-star-spacing': 0,
'no-control-regex': 0
}
}

16
.gitignore vendored
View File

@ -5,4 +5,18 @@
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
.DS_Store
.idea/
uploads/
node_modules/
server/node_modules/
dev/
dist/
sessions/
npm-debug.log
selenium-debug.log
server/resources/default/serviceAddr.json
.i18n.checker.json
package-lock.json
test/unit/coverage
test/e2e/reports

View File

@ -1,20 +1,31 @@
#### 从命令行创建一个新的仓库
# Skyline
```bash
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://git.trustie.net/Inspur/skyline.git
git push -u origin master
![image](https://forgeplus.trustie.net/projects/ga0fei/skyline/tree/master/skyline-logo.png?raw=true)
> Skyline是最好的OpenStack控制台对标OpenStack社区Horizon项目在易用性、页面性能等方面进行深度优化提供简单、易用、高效的OpenStack控制台。
## 配置修改
复制server/resouces/default/serviceAddr.template.json到server/resouces/default/serviceAddr.json
修改keystone配置项为openstack平台keystone模块Endpoint
## 代码构建
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:4001
npm run dev
# build for production with minification
npm run build
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
```
#### 从命令行推送已经创建的仓库
```bash
git remote add origin https://git.trustie.net/Inspur/skyline.git
git push -u origin master
```

20
babel.config.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
"plugins": [
["transform-class-properties", { "spec": true }],
["@babel/plugin-transform-classes", { "loose": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-transform-runtime",
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-json-strings",
"@babel/plugin-transform-modules-commonjs",
],
"presets": [
"@babel/preset-env"
]
};

28
build/build.js Normal file
View File

@ -0,0 +1,28 @@
require('./check-versions')()
require('shelljs/global')
const constants = require('../constants');
env.NODE_ENV = 'production'
const path = require('path')
const config = require('../config')
const webpack = require('webpack')
const webpackConfig = require('./webpack.prod.conf')
const logo = require('./logo');
const assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
mkdir('-p', constants.DIST_VIEWS)
cp('-R', 'static/*', assetsPath)
logo(function() {
webpack(webpackConfig, function (err, stats) {
if (err) {
console.error(err);
}
if (stats.hasErrors()) {
console.error(stats.toJson().errors);
}
});
});

45
build/check-versions.js Normal file
View File

@ -0,0 +1,45 @@
var semver = require('semver')
var chalk = require('chalk')
var packageConfig = require('../package.json')
var exec = function (cmd) {
return require('child_process')
.execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
{
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
}
]
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

93
build/dev-server-https.js Normal file
View File

@ -0,0 +1,93 @@
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
const fs = require('fs');
var path = require('path')
const tls = require('tls');
const https = require('https');
var express = require('express')
var webpack = require('webpack')
var ora = require('ora')
var proxyMiddleware = require('http-proxy-middleware')
require('shelljs/global');
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var constants = require('../constants');
console.warn(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
);
// var spinner = ora('building for dev-https...')
// spinner.start()
rm('-rf', constants.DEV)
mkdir('-p', constants.DEV)
mkdir('-p', constants.DEV_VIEWS)
mkdir('-p', path.join(constants.DEV, constants.STATIC))
cp('-R', 'static/*', path.join(constants.DEV, constants.STATIC) )
cp('-R', 'server/views/*.html', constants.DEV_VIEWS)
const server = require('../server/app')
var app = express()
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
// app.use(staticPath, express.static('./static'));
// app.use(staticPath, express.static(path.join(constants.DEV, staticPath)));
server(app);
/************************server配置***********************************/
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options))
})
var httpsPort = process.env.PORT || config.httpsDev.port
/************************webpack配置***********************************/
var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf')
var compiler = webpack(webpackConfig)
compiler.watch(200, function(err, stats) {
if (!!server && !!server.active) {
server.reload()
}
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
var uri = 'https://localhost:' + httpsPort;
console.warn('Server has been listening at ' + uri + '\n')
// spinner.stop();
});
app.use(require('connect-history-api-fallback')())
/**************************server启动*********************************/
var options = {
pfx:fs.readFileSync(path.posix.join(constants.ROOT_DIR, 'server/ssl/server.pfx')),
passphrase:'Inspur1!',
secureOptions: constants.SSL_OP_NO_TLSv1,
//ciphers: 'DHE-RSA-DES-CBC3-SHA',
ciphers: 'ECDHE-RSA-AES128-SHA',
}
tls.CLIENT_RENEG_LIMIT = 0;
module.exports = https.createServer(options, app).listen(httpsPort, "0.0.0.0", function(err){
if (err) {
return;
}
var uri = 'https://localhost:' + httpsPort;
console.log('https Server will be listening at ' + uri + "\n");
});

14
build/dev-server-only.js Normal file
View File

@ -0,0 +1,14 @@
const http = require('http');
const open = require('open');
require('shelljs/global');
env.NODE_ENV = 'development';
const app = require('../server/app');
console.warn(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
);
const httpServer = http.createServer(app);
httpServer.listen(4001, '0.0.0.0');
httpServer.listen(4001, 'localhost');
open('http://localhost:4001');

65
build/dev-spdy.js Normal file
View File

@ -0,0 +1,65 @@
var path = require('path')
require('shelljs/global');
env.NODE_ENV = 'development';
var constants = require('../constants');
rm('-rf', constants.DEV)
mkdir('-p', constants.DEV)
mkdir('-p', constants.DEV_VIEWS)
mkdir('-p', path.join(constants.DEV, constants.STATIC))
cp('-R', 'static/*', path.join(constants.DEV, constants.STATIC) )
const webpack = require('webpack');
const devMiddleware = require('webpack-dev-middleware');
const hotMiddleware = require('webpack-hot-middleware');
const open = require('open');
const logo = require('./logo');
const fs = require('fs');
const options = require('./webpack.dev.conf');
logo(function () {
const spdy = require('spdy');
const consts = require('constants');
const app = require('../server/app');
let compiler = webpack(options);
app.set('env', 'development');
app.use(devMiddleware(compiler, {
writeToDisk: true
}));
app.use(hotMiddleware(compiler, {
publicPath: '/'
}));
const port = 4001;
var sdpyOptions = {
key: fs.readFileSync(path.join(__dirname, '..', 'server/ssl/server-key.pem')),
cert: fs.readFileSync(path.join(__dirname, '..', 'server/ssl/server-cert.pem')),
secureOptions: consts.SSL_OP_NO_TLSv1,
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
spdy: {
protocols: [ 'h2', 'http/1.1' ],
plain: false,
'x-forwarded-for': true,
connection: {
windowSize: 1024 * 1024, // Server's window size
autoSpdy31: false
}
}
}
const spdyServer = spdy.createServer(sdpyOptions, app);
spdyServer.listen(port, '0.0.0.0');
spdyServer.listen(port, 'localhost');
let firstCompile = true;
compiler.hooks.done.tap('open broswer', stats => {
if (firstCompile) {
console.warn(`App(dev-spdy) running at:`);
console.warn(`- Local: https://localhost:${port}/`);
console.warn(`- Network: https://0.0.0.0:${port}/`);
console.warn(`Note that the development build is not optimized.`)
console.warn(`To create a production build, run npm run build.`)
open(`https://localhost:${port}/`);
firstCompile = false;
}
});
});

49
build/dev.js Normal file
View File

@ -0,0 +1,49 @@
var path = require('path')
require('shelljs/global');
env.NODE_ENV = 'development';
var constants = require('../constants');
rm('-rf', constants.DEV)
mkdir('-p', constants.DEV)
mkdir('-p', constants.DEV_VIEWS)
mkdir('-p', path.join(constants.DEV, constants.STATIC))
cp('-R', 'static/*', path.join(constants.DEV, constants.STATIC) )
const webpack = require('webpack');
const devMiddleware = require('webpack-dev-middleware');
const hotMiddleware = require('webpack-hot-middleware');
const open = require('open');
const logo = require('./logo');
const options = require('./webpack.dev.conf');
logo(function () {
const http = require('http');
const app = require('../server/app');
let compiler = webpack(options);
app.set('env', 'development');
app.use(devMiddleware(compiler, {
writeToDisk: true
}));
app.use(hotMiddleware(compiler, {
publicPath: '/'
}));
const port = 4001;
const httpServer = http.createServer(app);
httpServer.listen(port, '0.0.0.0');
httpServer.listen(port, 'localhost');
let firstCompile = true;
compiler.hooks.done.tap('open broswer', stats => {
if (firstCompile) {
console.warn(`App running at:`);
console.warn(`- Local: http://localhost:${port}/`);
console.warn(`- Network: http://0.0.0.0:${port}/`);
console.warn(`Note that the development build is not optimized.`)
console.warn(`To create a production build, run npm run build.`)
open(`http://localhost:${port}/`);
firstCompile = false;
}
});
});

15
build/logo.js Normal file
View File

@ -0,0 +1,15 @@
const figlet = require('figlet');
function logo(callback) {
figlet('skyline', {
font: 'Big'
}, function (err, data) {
if (err) {
throw err;
}
console.log(data);
callback();
});
}
module.exports = logo;

61
build/utils.js Normal file
View File

@ -0,0 +1,61 @@
var path = require('path')
var config = require('../config')
// var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
var sourceLoader = loaders.map(function (loader) {
var extraParamChar
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?')
extraParamChar = '&'
} else {
loader = loader + '-loader'
extraParamChar = '?'
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
}).join('!')
// Extract CSS when that option is specified
// (which is the case during production build)
// if (options.extract) {
// return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
// } else {
// return ['vue-style-loader', sourceLoader].join('!')
// }
}
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus'])
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader
})
}
return output
}

74
build/webpack.dev.conf.js Normal file
View File

@ -0,0 +1,74 @@
const path = require('path');
const constants = require('../constants');
const config = require('../config');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const utils = require('./utils');
const baseWebpackConfig = require('../webpack.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
for (let entryName in baseWebpackConfig.entry) {
baseWebpackConfig.entry[entryName].splice(0, 0, 'webpack-hot-middleware/client');
}
module.exports = merge(baseWebpackConfig, {
mode: 'development',
// devtool: '#eval-source-map',
output: {
path: constants.DEV,
publicPath: constants.ROOT_URL,
filename: utils.assetsPath('js/[name].js'),
chunkFilename: utils.assetsPath('js/[id].[name].js')
},
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env,
__DEV__: true,
__PROD__: false
}),
new HtmlWebpackPlugin({
filename: 'views/index.html',
template: path.posix.join(constants.VIEWS, 'index.html'),
inject: true,
hash: false,
cache: false,
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'views/login.html',
template: path.join(constants.VIEWS, 'login.html'),
inject: true,
hash: false,
cache: false,
chunks: ['login']
}),
new HtmlWebpackPlugin({
filename: 'views/timeout.html',
template: path.posix.join(constants.VIEWS, 'timeout.html'),
inject: true,
hash: false,
cache: false,
chunks: ['timeout']
}),
new HtmlWebpackPlugin({
filename: 'views/digitalVisualizationScreen.html',
template: path.posix.join(constants.VIEWS, 'digitalVisualizationScreen.html'),
inject: true,
hash: false,
cache: false,
chunks: ['digitalVisualizationScreen']
}),
new HtmlWebpackPlugin({
filename: 'views/loginWithToken.html',
template: path.posix.join(constants.VIEWS, 'loginWithToken.html'),
inject: true,
hash: true,
cache: false,
chunks: ['login-with-token']
}),
new webpack.HotModuleReplacementPlugin()
],
optimization: {
minimize: false
}
});

107
build/webpack.prod.conf.js Normal file
View File

@ -0,0 +1,107 @@
const path = require('path');
const constants = require('../constants');
const config = require('../config');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const utils = require('./utils');
const baseWebpackConfig = require('../webpack.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const { truncate } = require('fs');
module.exports = merge(baseWebpackConfig, {
mode: 'production',
devtool: false,
output: {
path: config.build.assetsRoot,
publicPath: constants.ROOT_URL,
filename: utils.assetsPath('js/[name].js'),
chunkFilename: utils.assetsPath('js/[id].[name].js')
},
plugins: [
new webpack.DefinePlugin({
'process.env': config.build.env,
__DEV__: false,
__PROD__: true
}),
new HtmlWebpackPlugin({
filename: 'views/index.html',
template: path.posix.join(constants.VIEWS, 'index.html'),
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'views/login.html',
template: path.posix.join(constants.VIEWS, 'login.html'),
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true
// removeAttributeQuotes: truncate
},
chunks: ['login']
}),
new HtmlWebpackPlugin({
filename: 'views/timeout.html',
template: path.posix.join(constants.VIEWS, 'timeout.html'),
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['timeout']
}),
new HtmlWebpackPlugin({
filename: 'views/digitalVisualizationScreen.html',
template: path.posix.join(constants.VIEWS, 'digitalVisualizationScreen.html'),
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['digitalVisualizationScreen']
}),
new HtmlWebpackPlugin({
filename: 'views/loginWithToken.html',
template: path.posix.join(constants.VIEWS, 'loginWithToken.html'),
inject: true,
hash: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunks: ['login-with-token']
})
],
optimization: {
minimize: true,
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\\/]node_modules[\\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
}
});

14
codeStyle Normal file
View File

@ -0,0 +1,14 @@
项目代码风格:
1. 根据eslint的错误提示修改
2. 命名
文件夹命名: 多个单词构成的文件名,各个单词全部小写,单词直接通过“-”连接。比如云监控文件夹 cloud-monitor
文件命名: vue后缀的文件采用大驼峰法命名即每个单词的首字母大写。比如CloudMonitor.vue
其他后缀的文件采用小驼峰法,即第一个单词首字母小写,后面其他单词首字母大写。
注:封装的类文件建议用大驼峰法命名
3. 静态资源添加,包括styles和img
位置: src/assets
图片位置src/assets/img/
styles位置: src/assets/styles
公共类资源位置src/assets/styles/common
组件类资源位置src/assets/styles/components
警告由于本项目多皮肤的实现方案的原因styles禁止写入单个vue文件,否则多皮肤将不生效

3
config/demo.env.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"demo"'
}

6
config/dev.env.js Normal file
View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

78
config/index.js Normal file
View File

@ -0,0 +1,78 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: {
NODE_ENV: '"production"'
},
port: 4001,
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: false,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css']
},
dev: {
env: {
NODE_ENV: '"development"'
},
port: 4001,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: true
},
httpsDev: {
env: {
NODE_ENV: '"development"'
},
port: 4001,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: true
},
demo: {
env: {
NODE_ENV: '"demo"'
},
port: 4001,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://127.0.0.1:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
// '^/api/keystone/v3': ''
}
},
'/node-api': {
target: 'http://127.0.0.1:3000',
changeOrigin: true,
pathRewrite: {
'^/node-api': ''
}
}
},
cssSourceMap: false
}
};

3
config/prod.env.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

6
config/test.env.js Normal file
View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

39
constants.js Normal file
View File

@ -0,0 +1,39 @@
/*
常量
*/
var path = require('path');
var DIR_NAME = __dirname;
exports.DEV = path.join(DIR_NAME, 'dev');
exports.DEV_VIEWS = path.join(DIR_NAME, 'dev/views/');
exports.DIST = path.join(DIR_NAME, 'dist');
exports.DIST_VIEWS = path.join(DIR_NAME, 'dist/views/');
/*
视图目录
*/
exports.SERVER = path.join(DIR_NAME, 'server');
exports.VIEWS = path.join(DIR_NAME, 'views');
exports.STATIC = '/static';
/*
对外服务根目录非编译文件
*/
exports.PUBLIC = '/public';
/*
程序根目录
*/
exports.ROOT_DIR = DIR_NAME;
/*
临时编译目录
*/
exports.BUILD_DIR = path.join(DIR_NAME, 'target');
exports.SRC_DIR = path.join(DIR_NAME, 'src');
exports.ARTIFACT_ID = 'inspur-git';
exports.ROOT_URL = '/';
exports.ASSETS = path.join(DIR_NAME, 'src/assets');
exports.LIB = path.join(DIR_NAME, 'static/lib');

129
package.json Normal file
View File

@ -0,0 +1,129 @@
{
"name": "@icos/skyline",
"version": "1.0.0",
"description": "UI Project of ICOS",
"author": "InCloud OpenStack UI Team",
"license": "COMMERCIAL",
"private": true,
"scripts": {
"install": "cd server && npm install",
"dev-server": "node build/dev-server-only.js",
"dev": "node --max_old_space_size=4096 build/dev.js",
"dev-spdy": "node --max_old_space_size=4096 build/dev-spdy.js",
"dev-https": "node --max_old_space_size=4096 build/dev-server-https.js",
"build": "node --max_old_space_size=4096 build/build.js",
"unit": "karma start test/unit/karma.conf.js --single-run",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"async-validator": "1.6.9",
"aws-sdk": "^2.743.0",
"d3": "^4.11.0",
"echarts": "3.7.1",
"element-ui": "^1.4.6",
"file-saver": "^2.0.2",
"ip-range-check": "^0.2.0",
"ip.js": "^1.3.2",
"jquery": "^3.5.1",
"js-base64": "^2.1.9",
"js-md5": "^0.4.2",
"js-pinyin": "^0.1.9",
"js-yaml": "^3.14.0",
"json2yaml": "^1.1.0",
"moment": "^2.27.0",
"underscore": "^1.11.0",
"vue": "^2.6.12",
"vue-clipboard2": "0.0.8",
"vue-cookie": "^1.1.4",
"vue-echarts-v3": "1.0.5",
"vue-i18n": "^5.0.3",
"vue-router": "^2.7.0",
"vuedraggable": "^2.24.1",
"vuex": "^2.1.2",
"wangeditor": "^3.1.1",
"xlsx": "^0.15.1",
"xterm": "^3.1.0",
"ztree": "^3.5.24"
},
"devDependencies": {
"@babel/core": "^7.11.5",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
"@babel/plugin-proposal-function-sent": "^7.8.3",
"@babel/plugin-proposal-json-strings": "^7.8.3",
"@babel/plugin-proposal-numeric-separator": "^7.8.3",
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
"@babel/plugin-syntax-class-properties": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-modules-commonjs": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/polyfill": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/preset-stage-2": "^7.8.3",
"autoprefixer": "^6.4.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"chai": "^3.5.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.1.0",
"cross-spawn": "^4.0.2",
"css-loader": "^0.25.0",
"ejs": "^2.5.6",
"eslint": "^3.19.0",
"eslint-config-standard": "^6.1.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^1.3.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^2.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.17.1",
"figlet": "^1.5.0",
"file-loader": "^0.9.0",
"function-bind": "^1.0.2",
"html-webpack-harddisk-plugin": "^1.0.2",
"html-webpack-plugin": "^4.4.1",
"http-proxy-middleware": "^0.17.2",
"inject-loader": "^2.0.1",
"json-loader": "^0.5.4",
"log4js": "^1.1.1",
"lolex": "^1.4.0",
"morgan": "^1.10.0",
"netjet": "^1.4.0",
"nightwatch": "^0.9.8",
"npm-check": "^5.9.2",
"open": "^7.2.1",
"opn": "^4.0.2",
"ora": "^0.3.0",
"postcss-loader": "^3.0.0",
"selenium-server": "2.53.1",
"semver": "^5.3.0",
"shelljs": "^0.7.4",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"spark-md5": "^3.0.1",
"spdy": "^4.0.2",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.12",
"walk": "^2.3.9",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-middleware": "^3.7.2",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^5.1.3",
"webpackbar": "^4.0.0"
},
"engines": {
"node": ">=10.0.0",
"npm": ">=6.0.0"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')()
]
}

100
server/RouterFactory.js Normal file
View File

@ -0,0 +1,100 @@
var walk = require('walk');
var path = require('path');
var _ = require('underscore')._;
var logger = require('log4js').getLogger('RouterFactory');
var scan = function (router, folder, context) {
var walker = walk.walkSync( path.join(global.DIR_NAME, folder), {
followLinks: false,
listeners: {
file: function (root, stats, next) {
var name = path.join(path.relative(folder, root), stats.name),
controller;
try {
controller = require(path.join(root, stats.name));
} catch (e) {
logger.error('Fail to load controller: ' + path.join(root, stats.name), e);
next();
}
if (controller.enabled === false) {
logger.info('Skip controller: ' + name);
next();
} else if(controller.routes) {
_.each(controller.routes, function(item) {
var methods = _.filter(['all', 'post', 'get'], function(method) {
if (controller[method]) {
var args = _.union([item.mapping], controller.middlewares && controller.middlewares() || [], [function(req, res, next) {
controller[method](req, res, {
html: item.html
});
}]);
router[method].apply(router, args);
return true;
}
return false;
});
logger.info('Initialized controller: %s mapping: %s [ %s ]', name, item.mapping, methods);
});
next();
} else {
var mapping = controller.mapping || '/' + path.join(path.relative(folder, root), stats.name.replace(/\.js$/, ''));
var methods = _.filter(['all', 'post', 'get'], function(method) {
if (controller[method]) {
var args = _.union([mapping], controller.middlewares && controller.middlewares(router) || [], [function(req, res, next) {
controller[method](req, res, next);
}]);
router[method].apply(router, args);
return true;
}
return false;
});
logger.info('Initialized controller: %s mapping: %s [ %s ]', name, mapping, methods);
next();
}
}
}
});
walker.on("errors", function (root, nodeStatsArray, next) {
logger.error(nodeStatsArray)
next();
});
walker.on("end", function () {
console.log("all done");
});
};
exports.mount = function (configurations, parentRouter, context) {
_.each(configurations, function (configuration) {
var router;
var name = configuration['name'] || configuration['class'] || configuration['mapping'] || '',
filePath = configuration['class'] ? './' + path.join('routes', configuration['class']) : './routes/default';
try {
router = require(filePath)(configuration, context);
} catch (e) {
logger.error('Fail to load router: '+filePath, e);
}
logger.info('Initialized router: %s mapping: %s', name, (configuration['mapping'] || '/'));
if (configuration['scan']) {
scan(router, configuration['scan'], context);
}
if (configuration['mapping']) {
parentRouter.use(configuration['mapping'], router);
} else {
parentRouter.use(router);
}
});
};

374
server/app.js Normal file
View File

@ -0,0 +1,374 @@
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;

49
server/bin/www Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env node
/****************************************************************************
To start in production env:
APP_ENV=prod NODE_ENV=production ./bin/www
In Beta
APP_ENV=beta NODE_ENV=production ./bin/www
In Dev
APP_ENV=dev NODE_ENV=development ./bin/www
****************************************************************************/
// https://github.com/shelljs/shelljs
const http = require('http');
const logger = require('log4js').getLogger('http');
process.env.NODE_ENV = 'production';
/**************************server启动*********************************/
const app = require('../app')
const context = require('../lib/Context').getCurrentContext();
let serverAddr = context.getResource('serviceAddr.json');
let bindip = serverAddr.bindip;
logger.warn("bindip:" + bindip);
var httpPort = 80;
const httpServer = http.createServer(app);
httpServer.listen(httpPort, bindip, function(err) {
});
const spdy = require('spdy');
const fs = require('fs');
const path = require('path');
const consts = require('constants');
var sdpyOptions = {
key: fs.readFileSync(path.join(__dirname, '..', 'ssl/server-key.pem')),
cert: fs.readFileSync(path.join(__dirname, '..', 'ssl/server-cert.pem')),
secureOptions: consts.SSL_OP_NO_TLSv1,
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
spdy: {
protocols: [ 'h2', 'http/1.1' ],
plain: false,
'x-forwarded-for': true,
connection: {
windowSize: 1024 * 1024,
autoSpdy31: false
}
}
};
const spdyPort = 443;
const spdyServer = spdy.createServer(sdpyOptions, app);
spdyServer.listen(spdyPort, bindip);

View File

@ -0,0 +1,28 @@
module.exports = {
KEY_LANGUAGE: 'language',
KEY_TOKEN: 'token',
PROJECT_TOKEN: 'ptoken', //项目token
KEY_ORG_ID: 'orgId',
KEY_ORG_NAME: 'orgName',
KEY_USER_ID: 'userId',
KEY_USER_NAME: 'userName',
KEY_PASSWORD: 'password',
KEY_ROLE_ID: 'roleId',
KEY_ROLE_TYPE: 'roleType',
KEY_OPE_LIST: 'opeList',
KEY_IS_LDAP: 'isLdap',
KEY_DOMAIN_ID: 'domainId',
KEY_DOMAIN_NAME: 'domainName',
KEY_REMOTE_HOST_IP: 'remoteHostIp',
KEY_REMOTE_PORT: 'remotePort',
KEY_REMOTE_ADDR: 'remoteAddr',
KEY_REMOTE_USER: 'remoteUser',
KEY_WARN_DAYS: 'warnDays',
KEY_PROJECT_LIST: 'projectList',
KEY_MENUCODE_LIST: 'menuCodeList',
KEY_SERVICES:'services',
KEY_REGIONS:"regions",
KEY_REGION_SERVICES:"regionServices",
KEY_SESSION_TIMEOUT:'sessionTimeout',
KEY_IP:"ip"
};

View File

@ -0,0 +1,40 @@
module.exports = {
/**
* REST API地址和方法.
*/
REST_URL: 'restUrl',
METHOD: 'method',
TIME_OUT_CONTROL: 'timeoutControl',
POLLING: 'polling',
REMOTE_IP: 'remoteIp',
REMOTE_PORT: 'remotePort',
/**
* HTTP协议
*/
HTTP: 'http:',
HTTPS: 'https:',
/**
* HTTP方法.
*/
GET: 'get',
POST: 'post',
PUT: 'put',
DELETE: 'delete',
/**
* 请求内容和期望得到的内容格式KEY值.
*/
KEY_CONTENT_TYPE: 'Content-Type',
KEY_ACCEPT: 'Accept',
/**
* 请求内容格式.
*/
CONTENT_TYPE: 'application/json; charset=utf-8',
/**
* 期望得到内容的格式Json.
*/
ACCEPT: 'application/json; charset=utf-8',
/**
* 期望得到内容的格式XML.
*/
ACCEPT_XML: 'application/xml; charset=utf-8'
}

View File

@ -0,0 +1,10 @@
module.exports = {
KEY_RESULT: 'result',
KEY_DATA: 'data',
KEY_TOTAL: 'total',
KEY_FLAG: 'flag',
KEY_ERRCODE: 'errCode',
KEY_RESDATA: 'resData',
RESULT_SUCCESS: 'success',
RESULT_FAILED: 'failed'
}

View File

@ -0,0 +1,8 @@
module.exports = {
// 重放攻击检查
REQ_IDENTIFIER: 'req-identifier'
, KEY_IDENTIFIER_LIST: 'identifier-list'
, MAX_TIME_DIFF_SEC: 5 * 60 // 最大时间戳差值
, MAX_IDENTIFIER_LIFE_SEC: 5 * 60 // 标识最大保存时间
, KEY_CLIENT_SERVER_TIME_DIFF: 'client-server-time-diff' // 客户端 服务器时间差
};

View File

@ -0,0 +1,5 @@
module.exports = {
IDENTITY_KEY: 'skey',
S3AK: 'S3AK',
S3SK: 'S3SK'
};

14
server/constants/index.js Normal file
View File

@ -0,0 +1,14 @@
const LoginConstants = require('./LoginConstants');
const ResultConstants = require('./ResultConstants');
const SessionConstants = require('./SessionConstants');
const RestConstants = require('./RestConstants');
const SecurityConstants = require('./SecurityConstants');
module.exports =
Object.assign(
{},
LoginConstants,
ResultConstants,
SessionConstants,
RestConstants,
SecurityConstants
);

View File

@ -0,0 +1,35 @@
const SessionHelper = require('../lib/SessionHelper');
const logger = require('log4js').getLogger('digitalVisualizationScreen');
const { getOEM } = require('../lib/util');
exports.enabled = true;
exports.mapping = '/digitalVisualizationScreen.html';
exports.middlewares = function (router) {
return [
function (req, res, next) {
let serviceAddr = context.getResource('serviceAddr.json');
let session = req.session;
session.arch = serviceAddr.arch || 'x86';
getOEM(serviceAddr['keystone']).then(res => {
session = Object.assign(session, res);
next();
}).catch(e => {
next();
});
},
function (req, res, next) {
next();
}
];
};
/**
* 路由请求成功回调函数
* @required
* @param {{}} req [request]
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function (req, res) {
let sessParam = SessionHelper.getSession(req, res) || {};
res.render('digitalVisualizationScreen', sessParam);
};

489
server/controllers/index.js Normal file
View File

@ -0,0 +1,489 @@
const SessionHelper = require('../lib/SessionHelper');
const urlApi = require('url');
const request = require('request');
const Consts = require('../constants');
const { getOEM } = require('../lib/util');
const logger = require('log4js').getLogger('INDEX');
const uuid = require('uuid');
exports.enabled = true;
exports.mapping = '/index.html';
exports.middlewares = function(router) {
let __DEV__ = process.env.NODE_ENV === 'development';
let serviceAddr = context.getResource('serviceAddr.json');
return [
//获取OEM信息
function(req, res, next) {
getOEM(serviceAddr['keystone']).then(res => {
let session = req.session;
session = Object.assign(session, res);
next();
}).catch(e => {
next();
});
},
//获取项目列表
function(req, res, next) {
let session = req.session;
let restUrl = "/v3/auth/projects";
session.poc = !!serviceAddr.poc;
session.objectStorageType = serviceAddr.objectStorageType || 'S3';
session.S3SuperUser = serviceAddr.S3SuperUser || {};
session.showSafeDelete = serviceAddr.showSafeDelete || false;
session.showManageConnect = serviceAddr.showManageConnect || false;
session.uploadImageUseFakeS3 = 'uploadImageUseFakeS3' in serviceAddr ? serviceAddr.uploadImageUseFakeS3 : true;
session.sdsipaddress = serviceAddr.sdsipaddress || '';
session.forceCheckCurrentUser = 'forceCheckCurrentUser' in serviceAddr ? serviceAddr.forceCheckCurrentUser : false;
session.forceVNCPassword = 'forceVNCPassword' in serviceAddr ? serviceAddr.forceVNCPassword : false;
session.arch = serviceAddr.arch || 'x86';
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
request(options, function(error, response, body) {
if (body && 'error' in body && String(body.error.code) === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html') : '/timeout.html';
res.redirect(loginUrl+"?__="+new Date().getTime());
return;
}
let projectList = [];
let projects = body.projects || [];
let flg = false;
let roleId = req.cookies.roleType;
let projectName;
let projectId = req.cookies.pid;
let roleType = req.cookies.roleType;
projects.forEach((item, key) => {
//用户cookie project id可用
//考虑切换环境情况
if (item.project_id === projectId&&item.role_type === parseInt(roleType)) {
flg = true;
roleId = item.role_id;
projectName = item.project_name;
}
projectList.push({
name: item.project_name,
id: item.project_id,
roleId:item.role_id,
roleType:item.role_type,
uuid:item.project_id+"&"+item.role_type,
active: (item.project_id === projectId && item.role_type == roleType)
});
});
if (!flg) {
projectId =(projectList[0] || {}).id;
roleType = (projectList[0] || {}).roleType;
projectName = (projectList[0] || {}).project_name;
roleId = (projectList[0] || {}).roleId;
(projectList[0] || {}).active = true;
}
let options = {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24, // would expire after 1 day
httpOnly: false, // The cookie only accessible by the web server
signed: false // Indicates if the cookie should be signed
};
// Set cookie
res.cookie('pid', projectId||"", options); // options is optional
res.cookie('roleType', roleType==undefined?"":roleType, options);
res.cookie('roleId', roleId==undefined?"":roleId, options);
req.body['pid'] = projectId;
req.body['roleType'] = roleType;
req.body['projectName'] = projectName;
req.session['pid'] = projectId;
req.session['roleId'] = roleId;
req.session[Consts.KEY_ROLE_TYPE] = roleType;
req.session[Consts.KEY_PROJECT_LIST] = projectList;
req.session[Consts.KEY_ROLE_ID] = roleId;
req.session.save(function () {
next();
});
});
},
//获取菜单码
function(req, res, next) {
let session = req.session;
var body = req.body;
let restUrl = "/v3/inspur/auth/menus/"+(body.pid?(body.roleType==0?'-':body.pid):"null");
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
request(options, function(error, response, body) {
if ( (body.error || {}).code + "" === "400") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html?serialnumber=no') : '/timeout.html?serialnumber=no';
res.redirect(loginUrl);
return;
}
if ( (body.error || {}).code + "" === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html#') : '/timeout.html#';
res.redirect(loginUrl);
return;
}
let opeCodeList = {};
if (body.menus && body.menus.length>0) {
body.menus.forEach(function(item) {
let menuCode = item.menu_code;
opeCodeList[menuCode] = true;
});
}
req.session[Consts.KEY_MENUCODE_LIST] = opeCodeList;
next();
});
},
function (req, res, next) { // 如果用户没有任何项目则无法通过获取ptoken获取endpoints此时使用内置的leo账号获取leo的endpoint
let pid = req.body.pid;
if (pid) {
next();
} else {
let serviceAddr = context.getResource('serviceAddr.json');
let leoKeystonePassword = serviceAddr.leoKeystonePassword || '';
let host = urlApi.format({
protocol: Consts.HTTP,
host: serviceAddr['keystone']
});
let options = {
url: `${host}/v3/auth/tokens`,
method: Consts.POST,
json: true,
body: {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"name": "leo",
"domain": {
"name": "default"
},
"password": leoKeystonePassword
}
}
}
}
},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT
}
};
request(options, function(error, response, body) {
req.session[Consts.KEY_SERVICES] = {};
req.session[Consts.KEY_REGION_SERVICES] = {};
req.session[Consts.KEY_REGIONS] = [];
if (error) {
next();
} else if (body.error) {
next();
} else {
let catalog = body.token.catalog;
let regionId = req.cookies.region_id;
let activeRegionId = '';
let regionServiceObj = {};
let regions = [];
let leoIndex = body.token.catalog.findIndex((item) => {
return item.name === 'leo';
});
catalog[leoIndex].endpoints.filter((endpoint) => {
return endpoint.interface === 'admin';
}).forEach(region => {
regions.push({
region: region.region,
region_id: region.region_id,
active: region.region_id === regionId
});
if (region.region_id === regionId) {
activeRegionId = regionId;
}
});
if (activeRegionId === '') {
activeRegionId = regions[0].region_id;
regions[0].active = true;
}
body.token.catalog.forEach((service, index) => {
service.endpoints.forEach((endpoint, index1) => {
let urlRegExp = /^(https?:\/\/.*:\d*)\/?/;
if (endpoint.interface === 'admin') {
let matches = endpoint.url.match(urlRegExp);
if (matches) {
if (!regionServiceObj[endpoint.region_id]) {
regionServiceObj[endpoint.region_id] = {};
}
regionServiceObj[endpoint.region_id][service.name] = matches[1];
}
}
});
});
req.session[Consts.KEY_SERVICES] = regionServiceObj[activeRegionId];
req.session[Consts.KEY_REGION_SERVICES] = regionServiceObj;
req.session[Consts.KEY_REGIONS] = regions;
next();
}
});
}
},
//获取ptoken
function(req, res, next) {
let session = req.session;
let domainList = session.domainList;
let projectList = session.projectList;
let body = req.body;
let restUrl = "/v3/auth/tokens";
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let getPIDTraceId = 'req-'+uuid.v1();
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || "",
'X-Openstack-Request-Id': getPIDTraceId
}
};
let pid = body.pid;
let roleType = body.roleType;
let projectName = body.projectName;
let isLogin = false;
if (!pid) { // 无pid情况只记录登录
next();
isLogin = true;
reordLog('', session['loginTraceId']);
} else {
let bodyParam = {
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": session[Consts.KEY_TOKEN]
}
},
"scope": {
project: {
"id": pid
}
}
}
};
options.body = bodyParam;
let traceId = getPIDTraceId;
isLogin = !session[Consts.PROJECT_TOKEN];
if (isLogin) { // 如果是登陆则使用登陆的追踪ID
traceId = session['loginTraceId'];
}
request(options, function(error, response, body) {
if ( (body.error || {}).code + "" === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html#') : '/timeout.html#';
res.redirect(loginUrl);
return;
}
let pToken = response.headers["x-subject-token"];
let serviceObj = {};
let regionServiceObj = {};
let regionList = [];
let regionId = req.cookies.region_id;
let activeRegionId = "";
if (body.token&&body.token.catalog) {
//先查看有几个region并记录下来
let keystoneIndex = body.token.catalog.findIndex((item) => {
return item.name === "keystone";
});
let regions = body.token.catalog[keystoneIndex].endpoints.filter((item) => {
return item.interface === "admin";
});
let flg = false;
regions.forEach((item, index) => {
regionList.push({
region: item.region,
region_id: item.region_id,
active: item.region_id === regionId
});
if (item.region_id === regionId) {
activeRegionId = item.region_id;
flg = true;
}
});
if (!flg) {
regionList[0].active = true;
activeRegionId = regionList[0].region_id;
}
let options = {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24, // would expire after 1 day
httpOnly: false, // The cookie only accessible by the web server
signed: false // Indicates if the cookie should be signed
};
// Set cookie
res.cookie('region_id', activeRegionId, options); // options is optional
//过滤
body.token.catalog.forEach((service, index) => {
service.endpoints.forEach((endpoint, index1) => {
let urlRegExp = /^(https?:\/\/.*:\d*)\/?/;
if (endpoint.interface === 'admin') {
let matches = endpoint.url.match(urlRegExp);
if (matches) {
if (!regionServiceObj[endpoint.region_id]) {
regionServiceObj[endpoint.region_id] = {};
}
regionServiceObj[endpoint.region_id][service.name] = matches[1];
}
}
});
});
}
req.session[Consts.PROJECT_TOKEN] = pToken;
req.session[Consts.KEY_SERVICES] = regionServiceObj[activeRegionId];
req.session[Consts.KEY_REGION_SERVICES] = regionServiceObj;
req.session[Consts.KEY_REGIONS] = regionList;
next();
reordLog(activeRegionId, traceId);
});
}
function reordLog(regionId, traceId) {
//记录登陆和切换项目日志
let o = {
url: url+"/v3/inspur/logs",
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'language': session[Consts.KEY_LANGUAGE] || "",
'X-Auth-Token':session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || "")
}
}
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
o.body = {
logs:[{
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:regionId||"",
project_id:pid?(roleType==0?"":pid):"",
target:"user",
log_level:"info",
ip:session[Consts.KEY_IP],
create_time:time,
description:"user"+session[Consts.KEY_USER_NAME]+" login",
language:"en",
trace_id: traceId
}, {
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:regionId||"",
project_id:pid?(roleType==0?"":pid):"",
target:"用户",
log_level:"信息",
ip:session[Consts.KEY_IP],
create_time:time,
description:"用户:"+session[Consts.KEY_USER_NAME]+"登录",
language:"zh_cn",
trace_id: traceId
}]
}
if (!isLogin) { //代表切换项目
if (req.cookies.switch!="region") {
if (roleType==0) {
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter all";
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入所有";
} else {
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter project:"+(projectName||"");
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入项目:"+(projectName||"");
}
} else if (req.cookies.switch=="region"){
let region = req.cookies.region_id;
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter region:"+(region||"");
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入region:"+(region||"");
}
res.cookie('switch', "", {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24,
httpOnly: false,
signed: false
});
request(o, function () {})
} else { //如果没有ptoken代表刚登陆进来
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" login";
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+"登录";
request(o, function () {})
}
}
}
];
};
/**
* 路由请求成功回调函数
* @required
* @param {{}} req [request]
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function(req, res) {
// let options = {
// maxAge: 1000 * 60 * 15, // would expire after 15 minutes
// httpOnly: true, // The cookie only accessible by the web server
// signed: true // Indicates if the cookie should be signed
// }
// // Set cookie
// res.cookie('cookieName', 'cookieValue', options) // options is optional
let sessParam = SessionHelper.getSession(req, res) || {};
// Object.assign(sessParam, sessParam.opeList);
// sessParam.opeList = JSON.stringify(sessParam.opeList);
res.render('index', sessParam);
};

View File

@ -0,0 +1,30 @@
const SessionHelper = require('../lib/SessionHelper');
const { getOEM } = require('../lib/util');
exports.enabled = true;
exports.mapping = '/login.html';
exports.middlewares = function (router) {
return [
function (req, res, next) {
let session = req.session;
let serviceAddr = context.getResource('serviceAddr.json');
session.arch = serviceAddr.arch || 'x86';
getOEM(serviceAddr['keystone']).then(res => {
session = Object.assign(session, res);
next();
}).catch(e => {
next();
});
}
];
};
/**
* 路由请求成功回调函数
* @required
* @param {{}} req [request]
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function (req, res) {
let sessParam = SessionHelper.getSession(req, res) || {};
res.render('login', sessParam);
};

View File

@ -0,0 +1,28 @@
const logger = require('log4js').getLogger('Login');
exports.enabled = true;
exports.mapping = '/icos/loginWithToken.html';
exports.middlewares = function (router) {
return [
function (req, res, next) {
next();
},
function (req, res, next) {
next();
}
];
};
/**
* 路由请求成功回调函数
* @required
* @param {{}} req [request]
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function (req, res) {
// let remoteAddress = req.headers['x-forwarded-for'] ||
// req.connection.remoteAddress ||
// req.socket.remoteAddress ||
// req.connection.socket.remoteAddress;
// let localAddress = req.connection.localAddress;
res.render('loginWithToken');
};

View File

@ -0,0 +1,34 @@
const logger = require('log4js').getLogger('timeout');
const { getOEM } = require('../lib/util');
exports.enabled = true;
exports.mapping = '/timeout.html';
exports.middlewares = function (router) {
return [
function (req, res, next) {
let serviceAddr = context.getResource('serviceAddr.json');
let session = req.session;
session.arch = serviceAddr.arch || 'x86';
getOEM(serviceAddr['keystone']).then(res => {
session = Object.assign(session, res);
next();
}).catch(e => {
next();
});
}
];
};
/**
* 路由请求成功回调函数
* @required
* @param {{}} req [request]
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function (req, res) {
// let remoteAddress = req.headers['x-forwarded-for'] ||
// req.connection.remoteAddress ||
// req.socket.remoteAddress ||
// req.connection.socket.remoteAddress;
// let localAddress = req.connection.localAddress;
res.render('timeout');
};

54
server/lib/Context.js Normal file
View File

@ -0,0 +1,54 @@
var fs = require('fs'),
logger = require('log4js').getLogger('Global');
var currentContext = {
_resource: {},
_env: 'prod',
setEnv: function (env) {
logger.info('Set Environment ' + env);
currentContext._env = env;
},
setResource: function (name, value) {
return currentContext._resource[name] = value;
},
getResource: function (name) {
var context = currentContext,
content;
if (context._resource[name]) {
return context._resource[name];
} else {
try {
var fileDefaultName = global.DIR_NAME + '/resources/default/' + name;
var fileEnvName = global.DIR_NAME + '/resources/' + context._env + '/' + name;
var fileName = fs.existsSync(fileEnvName) ? fileEnvName : fs.existsSync(fileDefaultName) ? fileDefaultName : '';
logger.info('Loading resource ' + fileName);
if (fileName) {
content = fs.readFileSync(fileName);
if (/\.json/.test(name)) {
content = JSON.parse(content);
}
context._resource[name] = content;
} else {
logger.error('No such resource: ' + fileEnvName);
}
} catch (e) {
logger.error('Error in loading resource: ' + name + ',' + e.message)
}
return content;
}
}
};
exports.getCurrentContext = function () {
return currentContext;
};

View File

@ -0,0 +1,58 @@
module.exports = function() {
//登录拦截器
// create an auth filter
var filter = require('express-authfilter').create({
// private zone, true for white list mode
deny: [
'/',
'/index.html',
// '/physical-resource.html',
'virtual-resource.html',
'digitalVisualizationScreen.html',
'/forceModifyPassword.html'
],
// public zone
allow: [ //白名单
'/login.html',
'/loginWithToken.html',
'/timeout.html',
'/__webpack_hmr',
/^\/static\/[a-z0-9_\-\/.%]+/i,
'/error.html',
/^\/login-api\/[a-z0-9_\-\/.%]+/i,
/^\/api\/[a-z0-9_\-\/.%]+/i,
/^\/node-api\/[a-z0-9_\-\/.%]+/i,
/^\/custom-api\/[a-z0-9_\-\/.%]+/i,
/^\/pim-api\/[a-z0-9_\-\/.%]+/i,
/^\/s3-api\/[a-z0-9_\-\/.%]+/i,
/^\/storage-api\/[a-z0-9_\-\/.%]+/i,
/^\/webSocket\/[a-z0-9_\-\/.%]+/i,
/^.+\.js/g,
/^.+\.css/g,
/^.+\.jpg/g,
/^.+\.jpeg/g,
/^.+\.png/g,
/^.+\.gif/g,
/^.+\.ico/g,
/^.+\.woff/g,
/^.+\.woff2/g,
/^.+\.ttf/g,
/^.+\.do/g,
/^.+\.mp3/g,
/^.+\.map/g,
/^.+\.html/g
],
// check user login state
check: function(req, res) {
return Boolean(req.session.token);
},
// login method
login: function(req, res) {
let loginUrl = req.baseUrl ? (req.baseUrl + '/login.html') : '/login.html';
res.redirect(loginUrl+"?__="+new Date().getTime());
}
});
return filter;
};

View File

@ -0,0 +1,92 @@
const logger = require('log4js').getLogger('SessionHelper');
const urlApi = require('url');
//Consts
const Consts = require('../constants');
const request = require('request');
const util = require('./util');
class SessionHelper {
static setSession(req, res, cb) {
let bodyParam = req.body;
req.session.cookie.maxAge = bodyParam.sessionTimeout*60*1000;
// req.session._garbage = Date.now();
// req.session.touch();
req.session[Consts.KEY_TOKEN] = bodyParam.token;
req.session[Consts.KEY_SESSION_TIMEOUT] = bodyParam.sessionTimeout; // 系统设置的session超时时间
req.session[Consts.KEY_IP] = bodyParam.ip;
// req.session[Consts.KEY_ORG_ID] = bodyParam.orgId;
// req.session[Consts.KEY_ORG_NAME] = bodyParam.orgName;
req.session[Consts.KEY_USER_ID] = bodyParam.userId;
req.session[Consts.KEY_USER_NAME] = bodyParam.userName;
// req.session[Consts.KEY_PASSWORD] = bodyParam.password;
req.session[Consts.KEY_LANGUAGE] = bodyParam.language;
req.session[Consts.KEY_DOMAIN_ID] = bodyParam.domainId;
req.session[Consts.KEY_DOMAIN_NAME] = bodyParam.domainName;
req.session['loginTraceId'] = bodyParam.traceId;
req.session.save(function() {
cb();
});
// 保存远程主机信息
// req.hostname / req.ip获取主机名和IP地址
// logger.info("locale:" + req.getLocale());
// req.session[Consts.KEY_REMOTE_HOST_IP] = req.getRemoteHost()); // IP
// req.session[Consts.KEY_REMOTE_PORT, req.getRemotePort()); // PORT
// req.session[Consts.KEY_REMOTE_ADDR, req.getRemoteAddr()); // ADDR
// req.session[Consts.KEY_REMOTE_USER, req.getRemoteUser()); // User
// 清空当前用户操作码信息 -- 解决切换后操作码信息缓存的问题
// req.session[Consts.KEY_OPE_LIST] = null;
}
static getSession(req, res) {
return req.session;
}
static setPrivilege(req, res, cb) {
let session = req.session;
let serviceAddr = global.context.getResource('serviceAddr.json');
let url = urlApi.format({
protocol: Consts.HTTP,
// hostname: '',
// port: 8080,
host: serviceAddr['ibase']
});
let options = {
url: urlApi.resolve(url, '/ibase/v1/role/user-menu-list'),
method: Consts.GET,
json: true,
body: {},
qs: {
page: 1,
pageSize: 9999
},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'auth-token': session[Consts.KEY_TOKEN]
}
};
request(options, function(err, response, body) {
let opeCodeList = {};
if (response.statusCode === 200 && body.flag) {
let data = (body.resData || {}).data;
data.forEach(function(item) {
let menuCode = item.menuCode;
util.ns(menuCode, opeCodeList);
});
}
req.session[Consts.KEY_OPE_LIST] = opeCodeList;
cb();
});
}
static setVerifyCode(req, type, verifyCode) {
let query = req.query;
req.session[type] = verifyCode;
}
}
module.exports = SessionHelper;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

100
server/lib/util.js Normal file
View File

@ -0,0 +1,100 @@
const SessionHelper = require('../lib/SessionHelper');
const logger = require('log4js').getLogger('Login');
const request = require('request');
const urlApi = require('url');
const Consts = require('../constants');
const fs = require('fs');
const path = require('path');
exports.readHTML = function(path, callback) {
fs.readFile(path.join(global.root, "views", path), "UTF8", function(err, data) {
});
};
exports.readFile = function(path, callback) {
fs.readFile(path, "UTF-8", callback);
};
exports.readFileSync = function(path) {
return fs.readFileSync(path, "UTF-8");
};
exports.responseData = function(response, data) {
response.send({ ret: true, data: data });
};
exports.responseError = function(response, errmsg) {
response.send({ ret: false, errmsg: errmsg });
};
exports.ns = function(name, root) {
var part = root || global,
parts = name && name.split('.') || [];
parts.forEach(function(partName, index, array) {
if (partName) {
part = part[partName] || (part[partName] = (index == array.length - 1 ? { flg: true } : {}));
}
});
return part;
};
exports.getOEM = function(host) {
let restUrl = `/v3/inspur/customparam/oem`;
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+restUrl,
method: Consts.GET,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': ''
}
};
return new Promise((resolve, reject) => {
request(options, function(error, response, body) {
if (!error) {
let result = {
oemSwitch: false,
oemProductLogo: '',
oemBrandLogo: '',
oemProductName: '',
oemVersion: '',
oemEmail: '',
oemAddress: '',
oemCopyright: ''
};
try {
if ('oem_switch' in body && body.oem_switch instanceof Array && body.oem_switch.length > 0) {
result.oemSwitch = body.oem_switch[0].value === '1';
if ('oem_product_logo' in body && body.oem_product_logo instanceof Array && body.oem_product_logo.length > 0) {
result.oemProductLogo = body.oem_product_logo[0].value;
}
if ('oem_brand_logo' in body && body.oem_brand_logo instanceof Array && body.oem_brand_logo.length > 0) {
result.oemBrandLogo = body.oem_brand_logo[0].value;
}
if ('oem_info' in body && body.oem_info instanceof Array && body.oem_info.length > 0) {
let oemInfo = JSON.parse(body.oem_info[0].value);
result.oemProductName = 'productName' in oemInfo ? oemInfo.productName : '';
result.oemVersion = 'version' in oemInfo ? oemInfo.version : '';
result.oemEmail = 'email' in oemInfo ? oemInfo.email : '';
result.oemAddress = 'address' in oemInfo ? oemInfo.address : '';
result.oemCopyright = 'copyright' in oemInfo ? oemInfo.copyright : '';
}
}
} catch (e) {
console.error(e);
}
resolve(result);
} else {
reject(error);
}
});
});
}

54
server/package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "@icos/skyline-server",
"version": "1.0.0",
"description": "UI Web Server Project of ICOS",
"author": "InCloud OpenStack UI Team",
"license": "COMMERCIAL",
"private": true,
"scripts": {
"serve": "node bin/www"
},
"dependencies": {
"aws-sdk": "^2.744.0",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"connect-redis": "^5.0.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"express-authfilter": "^1.0.2",
"express-mysql-session": "^2.1.4",
"express-session": "^1.17.1",
"express-xml-parser": "^1.0.0",
"file-concat-stream": "^0.1.3",
"formidable": "^1.1.1",
"fs-extra": "^5.0.0",
"ftp": "^0.3.10",
"gd-bmp": "^1.0.2",
"helmet": "^3.21.1",
"http-errors": "^1.8.0",
"http-proxy": "^1.18.1",
"js-md5": "^0.7.3",
"js2xmlparser": "^4.0.1",
"jsonfile": "^6.0.1",
"log4js": "^1.1.1",
"moment": "^2.27.0",
"morgan": "^1.7.0",
"netjet": "^1.4.0",
"nunjucks": "^3.0.0",
"ora": "^0.3.0",
"promise-mysql": "^4.1.3",
"request": "^2.88.2",
"safe-publish-latest": "^1.1.4",
"serve-favicon": "^2.5.0",
"session-file-store": "^1.4.0",
"spark-md5": "^3.0.1",
"spdy": "^4.0.2",
"underscore": "^1.11.0",
"walk": "^2.3.14"
},
"devDependencies": {},
"engines": {
"node": ">=10.0.0",
"npm": ">=6.0.0"
}
}

View File

@ -0,0 +1,6 @@
{
"appenders": [
{ "type": "console" }
],
"replaceConsole": true
}

View File

@ -0,0 +1,144 @@
[
{
"name": "api",
"class": "api",
"mapping": "/api",
"upstream": "api-proxy"
},
{
"name": "customApi",
"class": "customApi",
"mapping": "/custom-api"
},
{
"name": "s3Api",
"class": "s3Api",
"mapping": "/s3-api"
},
{
"name": "root",
"mapping": "/",
"scan": "./controllers"
},
{
"name": "skin",
"class": "skin",
"mapping": "/static/css"
},
{
"name": "static",
"class": "static",
"mapping": "/"
},
{
"name": "login",
"class": "login/login",
"mapping": "/node-api/keystone/login"
},
{
"name": "getVerifyCodeConfig",
"class": "login/getVerifyCodeConfig",
"mapping": "/node-api/keystone/getVerifyCodeConfig"
},
{
"name": "getVerifyCode",
"class": "login/getVerifyCode",
"mapping": "/node-api/keystone/getVerifyCode"
},
{
"name": "saveLoginInfo",
"class": "login/saveLoginInfo",
"mapping": "/node-api/login"
},
{
"name": "logout",
"class": "login/logout",
"mapping": "/node-api/logout"
},
{
"name": "resetPassword",
"class": "login/resetPassword",
"mapping": "/node-api/keystone/resetPassword"
},
{
"name": "projectToken",
"class": "project/pToken",
"mapping": "/node-api/keystone/project-token"
},
{
"name": "uploadTemplate",
"class": "upload/uploadTemplate",
"mapping": "/node-api/uploadTemplate"
},
{
"name": "checkUrlExists",
"class": "upload/checkUrlExists",
"mapping": "/node-api/checkUrlExists"
},
{
"name": "uploadObjStorDoc",
"class": "upload/uploadObjStorDoc",
"mapping": "/node-api/uploadObjStorDoc"
},
{
"name": "recordLog",
"class": "log/recordLog",
"mapping": "/node-api/keystone/recordLog"
},
{
"name": "downloadObject",
"class": "download/downloadObject",
"mapping": "/node-api/keystone/downloadObject"
},
{
"name": "serialNumber",
"class": "serialNumber",
"mapping": "/node-api/keystone/serialNumber"
},
{
"name": "getToken",
"class": "login/getToken",
"mapping": "/node-api/getToken"
},
{
"name": "checkPassword",
"class": "login/checkPassword",
"mapping": "/node-api/checkPassword"
},
{
"name": "downloadReport",
"class": "download/downloadReport",
"mapping": "/node-api/downloadReport"
},
{
"name": "keystonePassword",
"class": "keystonePassword",
"mapping": "/node-api/keystone/password"
},
{
"name": "configBeforeAuth",
"class": "configBeforeAuth",
"mapping": "/node-api/keystone/config-before-auth"
},
{
"name": "fake-s3",
"class": "fake-s3/route",
"mapping": "/node-api/fake-s3"
},
{
"name": "storageApi",
"class": "storageApi",
"mapping": "/storage-api"
},
{
"name": "webSocket",
"class": "webSocket",
"mapping": "/webSocket",
"upstream": "webSocket-proxy"
},
{
"name": "pim",
"class": "node-api/pim",
"mapping": "/node-api/pim"
}
]

View File

@ -0,0 +1,23 @@
{
"keystone": "10.48.51.203/identity",
"poc": false,
"bindip":"0.0.0.0",
"twoFactorAuth": {
"switch": false,
"solution": "inspur_cert_v3"
},
"objectStorageType": "S3",
"S3SuperUser": {
"ak": "ak",
"sk": "sk"
},
"showSafeDelete": false,
"uploadImageUseFakeS3": true,
"sdsipaddress": "100.2.124.190:8080",
"encryptAlgorithm": "aes-256-cbc",
"encryptKey": "0123456789abcdef0123456789abcdef",
"encryptIV": "0123456789abcdef",
"leoKeystonePassword": "password",
"forceCheckCurrentUser": false,
"forceVNCPassword": false
}

245
server/routes/api.js Normal file
View File

@ -0,0 +1,245 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('API');
//Consts
const Consts = require('../constants');
const url = require('url');
const proxy = context.getResource('proxy');
const router = require('express').Router();
const _ = require('underscore');
// 路径的菜单码权限校验
router.use(function(req, res, next) {
let permissions = [];
permissions.push({ method: 'get', test: /^$/, anyOf: ['m.systemmanage'] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/domains\/config\/(.*)/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/customparam\/logo/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/users\/(.*)/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/serialnumber/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/system\/config\/file-conf/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/projects/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/users\/(.*)\/projects/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/(.*)\/users/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/users/, anyOf: [] });
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/users\/self/, anyOf: [] });
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/users\/self/, anyOf: [] });
permissions.push({ method: 'put', test: /^\/leo\/v1\/order\/get_approve_order_amount/, anyOf: [] });
permissions.push({ method: 'put', test: /^\/leo\/v1\/order\/get_applicant_order_list/, anyOf: [] });
permissions.push({ method: 'post', test: /^\/leo\/v1\/order\/create_order/, anyOf: [] });
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/auth\/menus\/-/, anyOf: [] });
// 组
// 组列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/groups/, anyOf: ['m.systemmanage.groupmanage', 'm.systemmanage.project.projectmanage'] });
// 新建组
permissions.push({ method: 'post', test: /^\/keystone\/v3\/groups/, anyOf: ['m.systemmanage.groupmanage.add'] });
// 编辑组
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/groups\/(.*)/, anyOf: ['m.systemmanage.groupmanage.edit'] });
// 删除组
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/groups\/(.*)/, anyOf: ['m.systemmanage.groupmanage.delete'] });
// 组赋权列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/assignments\/groups\/(.*)\/roles/, anyOf: ['m.systemmanage.groupmanage.assignrole'] });
// 组赋权
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/assignments\/groups\/(.*)\/roles/, anyOf: ['m.systemmanage.groupmanage.assignrole'] });
// 组赋权
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/assignments\/groups\/(.*)\/role\/(.*)/, anyOf: ['m.systemmanage.groupmanage.assignrole'] });
// 人员维护
permissions.push({ method: 'put', test: /^\/keystone\/v3\/groups\/(.*)\/users\/(.*)/, anyOf: ['m.systemmanage.groupmanage.personmanage'] });
// 人员维护
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/groups\/(.*)\/users\/(.*)/, anyOf: ['m.systemmanage.groupmanage.personmanage'] });
// IP黑白名单
// 获取IPSetting黑名单
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/system\/ipblacklist/, anyOf: ['m.systemmanage.ipsetting'] });
// 获取IPSetting白名单
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/system\/ipwhitelist/, anyOf: ['m.systemmanage.ipsetting'] });
// 新建IPSetting黑名单
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/system\/ipblacklist/, anyOf: ['m.systemmanage.ipsetting.add'] });
// 新建IPSetting白名单
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/system\/ipwhitelist/, anyOf: ['m.systemmanage.ipsetting.add'] });
// 删除IPSetting黑名单
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/system\/ipblacklist/, anyOf: ['m.systemmanage.ipsetting.delete'] });
// 删除IPSetting白名单
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/system\/ipwhitelist/, anyOf: ['m.systemmanage.ipsetting.delete'] });
// 编辑IPSetting黑名单
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/system\/ipblacklist/, anyOf: ['m.systemmanage.ipsetting.edit'] });
// 编辑IPSetting白名单
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/system\/ipwhitelist/, anyOf: ['m.systemmanage.ipsetting.edit'] });
// 角色管理
// 角色列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/roles/, anyOf: ['m.systemmanage.rolemanage', 'm.systemmanage.project.projectmanage'] });
// 权限列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/menus\/(.*)/, anyOf: ['m.systemmanage.rolemanage'] });
// 添加角色
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/roles/, anyOf: ['m.systemmanage.rolemanage.add'] });
// 删除角色
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/roles\/(.*)/, anyOf: ['m.systemmanage.rolemanage.delete'] });
// 查看角色
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/roles\/(.*)/, anyOf: ['m.systemmanage.rolemanage.detail'] });
// 编辑角色
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/roles\/(.*)/, anyOf: ['m.systemmanage.rolemanage.edit'] });
// 项目管理
// 项目列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/projects/, anyOf: ['m.systemmanage.projectmanage'] });
// 项目组列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/groups/, anyOf: ['m.systemmanage.projectmanage', 'm.systemmanage.project.projectmanage'] });
// 修改组
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/(.*)\/groups/, anyOf: ['m.systemmanage.projectmanage.updategroup', 'm.systemmanage.project.projectmanage'] });
// 修改组
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/(.*)\/groups/, anyOf: ['m.systemmanage.projectmanage.updategroup', 'm.systemmanage.project.projectmanage'] });
// 修改成员
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/(.*)/, anyOf: ['m.systemmanage.projectmanage.updateperson', 'm.systemmanage.project.projectmanage'] });
// 修改成员
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/inspur\/assignments\/projects\/(.*)/, anyOf: ['m.systemmanage.projectmanage.updateperson', 'm.systemmanage.project.projectmanage'] });
// 用户管理
// 用户列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/users/, anyOf: ['m.systemmanage.usermanage'] });
// 组织列表
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/departments/, anyOf: ['m.systemmanage.usermanage', 'm.operationmanage.flowmanage'] });
// 用户角色列表、权限详情
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/assignments\/users\/(.*)\/roles/, anyOf: ['m.systemmanage.usermanage', 'm.systemmanage.usermanage.userroledetail', 'm.systemmanage.usermanage.assignrole'] });
// 给用户删除角色(
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/assignments\/users\/(.*)\/role\/(.*)/, anyOf: ['m.systemmanage.usermanage.assignrole'] });
// 给用户添加角色
permissions.push({ method: 'post', test: /^\/keystone\/v3\/inspur\/assignments\/users\/(.*)\/roles/, anyOf: ['m.systemmanage.usermanage.assignrole'] });
// 添加用户
// permissions.push({ method: 'post', test: /^\/keystone\/v3\/users/, anyOf: ['m.systemmanage.usermanage.add'] });
// 删除用户
permissions.push({ method: 'delete', test: /^\/keystone\/v3\/users\/(.*)/, anyOf: ['m.systemmanage.usermanage.delete'] });
// 编辑、禁用用户、启用用户、解锁用户、重置密码
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/users\/(.*)/, anyOf: ['m.systemmanage.usermanage.edit', 'm.systemmanage.usermanage.disable', 'm.systemmanage.usermanage.enable', 'm.systemmanage.usermanage.unlock', 'm.systemmanage.usermanage.resetpassword'] });
// 参数设置
// 日志获取
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/logs\/param/, anyOf: ['m.systemmanage.paramsetting'] });
// 日志设置
permissions.push({ method: 'patch', test: /^\/keystone\/v3\/inspur\/logs\/param/, anyOf: ['m.systemmanage.paramsetting.logsetting'] });
// 删除日志
permissions.push({ method: 'delete', test: /\/keystone\/v3\/inspur\/logs/, anyOf: ['m.systemmanage.paramsetting.logsetting'] });
// 操作日志
// 获取日志
permissions.push({ method: 'get', test: /^\/keystone\/v3\/inspur\/logs/, anyOf: ['m.systemmanage.opreatelog'] });
let menuCodes = Consts.KEY_MENUCODE_LIST in req.session ? Object.keys(req.session[Consts.KEY_MENUCODE_LIST]) : [];
let menuCodeWhiteList = permissions.map(item => item.anyOf);
menuCodeWhiteList = _.flatten(menuCodeWhiteList);
let forbid = false;
let match = false;
// 如果当前访问的路径,在白名单的范围之内,则检查当前用户是否拥有访问该菜单的权限(菜单码),如果没有则拒绝访问
let currentPath = req.path.toLowerCase();
let currentMethod = req.method.toLowerCase();
for (let permission of permissions) {
if (permission.test.test(currentPath) && currentMethod === permission.method) {
match = true;
if (permission.anyOf.length === 0) {
forbid = false;
} else {
if (permission.anyOf.findIndex(menuCode => menuCodes.includes(menuCode)) === -1) {
forbid = true;
}
}
break;
}
}
// 如果当前所有的权限是白名单权限的子集,则当前管理员判断为安全管理员或审计管理员。此时限制访问其他接口
if (isSubSet(menuCodes, menuCodeWhiteList)) {
if (!match) {
forbid = true;
}
}
if (forbid) {
res.status(401);
res.end();
} else {
next();
}
});
router.use('/:service', function(req, res) {
let session = req.session;
let body = req.body;
let moduleName = req.params.service;
let services;
if (req.headers.regionid && req.headers.regionid !== req.cookies.region_id) { //调用其它region接口
services = session[Consts.KEY_REGION_SERVICES][req.headers.regionid];
} else {
services = session[Consts.KEY_SERVICES];
}
if (req.method === "OPTIONS" || req.method === "HEAD") {
res.status(403);
res.end();
}
if (!services) {
res.status(401);
res.end();
} else {
let service = services[moduleName];
if (!service && moduleName === "keystone") {
let serviceAddr = context.getResource('serviceAddr.json');
service = 'http://' + serviceAddr['keystone'];
}
if (service) {
if (moduleName === 'ironic') {
req.headers['X-OpenStack-Ironic-API-Version'] = 'latest';
}
if (moduleName === 'manila') {
req.headers['X-Openstack-Manila-Api-Version'] = '2.32';
}
if (moduleName === 'swift') {
service += '/swift';
}
let host = url.parse(service);
req.headers.host = host.hostname || '';
req.headers[Consts.KEY_CONTENT_TYPE] = Consts.CONTENT_TYPE;
req.headers[Consts.KEY_ACCEPT] = Consts.ACCEPT;
req.headers['X-Auth-Token'] = session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN]?session[Consts.KEY_TOKEN]:"");
// 会话时间
let polling = body[Consts.POLLING];
polling && (req.headers['auth-keep'] = 'false');
let language = session[Consts.KEY_LANGUAGE];
let date = new Date();
req.headers["request-time"] = date.getTime()+date.getTimezoneOffset()*60*1000;
req.headers["region-id"] = req.cookies.region_id||"";
req.headers["remote-ip"] = session[Consts.KEY_IP];
req.headers["language"] = language || "";
res.header("Cache-Control", "no-cache");
if (host.protocol === 'https:') {
proxy.web(req, res, {
target: service,
secure: false
});
} else {
proxy.web(req, res, {
target: service
});
}
} else {
res.status(503);
res.end();
}
}
});
/**
* 判断数组A是否为数组B的子集
* @param {*} a 数组A
* @param {*} b 数组B
*/
function isSubSet(a, b) {
let result = true;
for (let aItem of a) {
if (b.indexOf(aItem) === -1) {
result = false;
break;
}
}
return result;
}
return router;
};

View File

@ -0,0 +1,35 @@
module.exports = function(options, context) {
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../constants');
const request = require('request');
router.get('/', function(req, res) {
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: `${url}/v3/inspur/system/config/before-auth`,
method: Consts.GET,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': ''
}
};
res.header('Cache-Control', 'no-cache');
request(options, function(error, response, body) {
if (body && body.error) {
res.status(body.error.code);
}
res.json(body);
});
});
return router;
};

View File

@ -0,0 +1,41 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('CustomApi');
//Consts
const Consts = require('../constants');
var proxy = context.getResource('proxy');
var router = require('express').Router();
router.use("/:service", function(req, res) {
let session = req.session;
let body = req.body;
let restUrl = req.url;
// let baseUrl = req.baseUrl;
// let serviceAddr = context.getResource('serviceAddr.json');
let moduleName = req.params.service;
// let host = serviceAddr[moduleName];
// req.headers.host = host || "";
req.headers[Consts.KEY_CONTENT_TYPE] = Consts.CONTENT_TYPE;
req.headers[Consts.KEY_ACCEPT] = Consts.ACCEPT;
req.headers['X-Auth-Token'] = session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:session[Consts.KEY_TOKEN];
// 会话时间
let polling = body[Consts.POLLING];
polling && (req.headers['auth-keep'] = 'false');
let language = session[Consts.KEY_LANGUAGE];
req.headers["language"] = language || "";
let timeoutControl = body[Consts.TIME_OUT_CONTROL];
res.header("Cache-Control","no-cache");
if (req.query.reqType=="https") {
proxy.web(req, res, {
target: 'https://' + moduleName,
secure:false
});
} else {
proxy.web(req, res, {
target: 'http://' + moduleName
});
}
});
return router;
};

3
server/routes/default.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = function(options){
return require('express').Router();
}

View File

@ -0,0 +1,76 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('DownloadObject');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.get('/', function(req, res) {
/*res.set({
headers[Consts.KEY_CONTENT_TYPE]: "application/octet-stream"
});*/
let session = req.session;
let fields = req.fields;
let qParam = req.query;
let language = qParam["language"];
let containerName = qParam["containerName"];
let fileNameTemp = qParam["objectName"];
let projectId = qParam["projectId"];
fileNameTemp =encodeURI(fileNameTemp);
containerName = encodeURI(containerName);
projectId = encodeURI(projectId);
let restUrl = "/swift/v1/" + projectId + "/" + containerName + "/" + fileNameTemp;
//let restUrl = "/swift/v1/wdq";
if (!restUrl.startsWith('/') ) {
restUrl = '/' + restUrl;
}
let moduleName = restUrl.split('/')[1];
let host = session[Consts.KEY_SERVICES][moduleName];
/*let qsParam = {
"id": fujianId,
"ciId": ciid,
"model": model,
"ciName": ciName,
};*/
let options = {
url: urlApi.resolve(host, restUrl),
//if you expect binary data, you should set encoding: null
encoding : null, //让body 直接是buffer
method: Consts.GET,
// json: true,
// body: {},
// qs: qsParam,
headers: {
[Consts.KEY_CONTENT_TYPE]: 'application/json; charset=utf-8',
[Consts.KEY_ACCEPT]: 'application/octet-stream; charset=utf-8',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
request(options).on("response", function(response){
if(response.statusCode==200){
res.setHeader(Consts.KEY_CONTENT_TYPE,"application/octet-stream");
res.attachment(decodeURI(fileNameTemp));
response.on("data", function(data){
res.write(data, "binary");
});
response.on("end", function(){
res.end();
});
}else{
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'reqTimeout',
[Consts.KEY_RESDATA]: ''
});
}
});
});
return router;
};

View File

@ -0,0 +1,116 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('DownloadReport');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
const uuid = require('uuid');
router.use('/', function(req, res) {
/*res.set({
headers[Consts.KEY_CONTENT_TYPE]: "application/octet-stream"
});*/
let traceId = 'req-'+uuid.v1();
let session = req.session;
let fields = req.fields;
let qParam = req.query;
let taskId = qParam["taskId"];
let fileNameTemp = qParam["taskName"] + ".xlsx";
fileNameTemp =encodeURI(fileNameTemp);
let restUrl = "v1/report/download_excel?task_id=" + taskId;
if (!restUrl.startsWith('/') ) {
restUrl = '/' + restUrl;
}
let moduleName = restUrl.split('/')[1];
let host = session[Consts.KEY_SERVICES]["leo"];
let options = {
url: urlApi.resolve(host, restUrl),
//if you expect binary data, you should set encoding: null
encoding : null, //让body 直接是buffer
method: Consts.GET,
// json: true,
// body: {},
// qs: qsParam,
headers: {
[Consts.KEY_CONTENT_TYPE]: 'application/json; charset=utf-8',
// [Consts.KEY_ACCEPT]: 'application/octet-stream; charset=utf-8',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE],
'X-Openstack-Request-Id': traceId
}
};
request(options).on("response", function(response){
if(response.statusCode==200){
res.setHeader(Consts.KEY_CONTENT_TYPE,"application/octet-stream");
res.attachment(decodeURI(fileNameTemp));
response.on("data", function(data){
res.write(data, "binary");
});
response.on("end", function(){
res.end();
});
recordLog();
function recordLog() {
let logHost = session[Consts.KEY_SERVICES]['keystone'];
let o = {
url: logHost + "/v3/inspur/logs",
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'language': session[Consts.KEY_LANGUAGE] || "",
'X-Auth-Token':session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || "")
}
}
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
let projectId = req.cookies.pid;
let roleType = req.cookies.roleType;
o.body = {
logs:[{
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:req.cookies.region_id?req.cookies.region_id:"",
project_id:projectId?(roleType==0?"":projectId):"",
target:"Report",
log_level:"info",
ip:session[Consts.KEY_IP],
create_time:time,
description:"Download report: " + qParam["taskName"],
language:"en",
trace_id: traceId
}, {
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:req.cookies.region_id?req.cookies.region_id:"",
project_id:projectId?(roleType==0?"":projectId):"",
target:"报表",
log_level:"信息",
ip:session[Consts.KEY_IP],
create_time:time,
description:"下载报表: " + qParam["taskName"],
language:"zh_cn",
trace_id: traceId
}]
}
request(o, function () {})
}
}else{
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: response.statusCode,
[Consts.KEY_RESDATA]: ''
});
}
});
});
return router;
};

View File

@ -0,0 +1,681 @@
const exec = require('child_process').exec;
let concat = require('file-concat-stream');
let fs = require('fs-extra');
let os = require('os');
let js2xmlparser = require("js2xmlparser");
let jsonfile = require('jsonfile');
let path = require('path');
let SparkMD5 = require('spark-md5');
let uuidv4 = require('uuid/v4');
const logger = require('log4js').getLogger('default');
const IMAGE_UPLOAD_DIR = os.tmpdir();
const IMAGE_UPLOAD_FILE_DATA = path.join(IMAGE_UPLOAD_DIR, 'file-data');
const IMAGE_UPLOAD_PART_DATA = path.join(IMAGE_UPLOAD_DIR, 'part-data');
/**
* 设置响应格式
*/
function setResType(req, resp, next) {
resp.type('application/xml'); // 设置响应格式为xml, aws-sdk需要
next();
}
/**
* 查询已上传分片信息 - AWS.S3.listParts.
*/
function listParts(req, resp) {
let bucket = req.params.bucket;
let fileName = req.params.fileName;
let uploadId = req.query.uploadId;
let maxParts = req.query['max-parts'];
// 查询已上传分片 listParts
// 查询并返回指定uploadId的已上传分片信息
let partInfo = getPartInfoData(bucket, fileName, uploadId);
let content = {
Part: partInfo.Parts
};
resp.send(js2xmlparser.parse('listPartsResult', content));
};
/**
* 获取所有文件的上传进度.
*/
function getAllFileUploadProgress(req, resp) {
let bucket = req.params.bucket;
let showDetail = req.query.detail || false;
let uploads = getBucketUploads(bucket);
let results = [];
uploads.forEach((upload) => {
let fileName = upload.Key;
let uploadId = upload.UploadId;
let uploaded = 0;
let partInfo = getPartInfoData(bucket, fileName, uploadId);
partInfo.Parts.forEach((part) => {
uploaded += part.Size;
});
results.push({
Key: fileName,
Uploaded: uploaded,
Parts: showDetail ? partInfo.Parts : []
});
});
resp.header('Cache-Control', 'no-cache');
resp.send(results);
}
/**
* 创建分片上传任务 - AWS.S3.createMultipartUpload
* 完成分片上传 - AWS.S3.completeMultipartUpload.
*/
function createOrCompleteMultipartUpload(req, resp) {
let bucket = req.params.bucket;
let fileName = req.params.fileName;
if (req.query && req.query.hasOwnProperty('uploads')) {
// 创建分片上传任务 - AWS.S3.createMultipartUpload
let uploadId = uuidv4();
// 保存上传任务
addUploadTask(bucket, {
Key: fileName,
UploadId: uploadId,
Initiated: (new Date()).getTime()
});
let content = {
UploadId: uploadId
};
resp.send(js2xmlparser.parse('initMultipartUploadResult', content));
} else {
// 完成分片上传 - AWS.S3.completeMultipartUpload
let uploadId = req.query.uploadId;
let parts = req.body.CompleteMultipartUpload.Part;
let spark = new SparkMD5();
let dataPath = path.join(IMAGE_UPLOAD_PART_DATA, uploadId);
let filePath = path.join(IMAGE_UPLOAD_FILE_DATA, bucket);
fs.mkdirpSync(filePath);
let partDataFileList = [];
// TODO 检查PartNumber的排序
parts.forEach(part => {
let partNumber = part.PartNumber;
partDataFileList.push(path.join(dataPath, partNumber + '.data'));
// 通过分片etag计算文件etag
let etag = part.ETag;
spark.append(etag);
});
let fileEtag = spark.end();
// TODO 分片数据etag校验
// 合并生成文件 ? 大文件合并的场合会不会影响上传请求
// 自己实现concat方法, 输出合并进度 单独提供合并进度的接口
// TODO 有没有快速合并文件的方法、命令。。。
concatFile(uploadId, partDataFileList, path.join(filePath, fileName), () => {
// 清除上传信息
// clearUploadInfo(bucket, uploadId);
}, err => {
logger.error('error concat part file', err.stack);
});
// 考虑文件合并可能需要较长时间 - 文件合并前提前返回
resp.header('etag', fileEtag);
let content = {
Bucket: bucket,
Key: fileName,
ETag: fileEtag
};
resp.send(js2xmlparser.parse('completeMultipartUploadResult', content));
}
}
/**
* 查询文件合并进度.
*/
function getFileConcatProgress(req, resp) {
let uploadId = req.query.uploadId;
resp.type('application/json');
let concatInfo = concatInfoMap[uploadId];
resp.send(concatInfo);
}
/**
* {
* 'uploadId': {
* total: 20,
* concat: 10
* }
* }
*/
let concatInfoMap = {};
function concatFile(uploadId, files, target, onSuccess, onError) {
var writeStream = fs.createWriteStream(target);
let concatInfo = {
total: files.length,
concat: 0
};
writeFiles(concatInfo, files.reverse(), writeStream, onSuccess, onError);
concatInfoMap[uploadId] = concatInfo;
}
function writeFiles(concatInfo, files, writeStream, onSuccess, onError) {
if (files.length) {
var currentFile = files.pop();
var readStream = fs.createReadStream(currentFile);
readStream.pipe(writeStream, { end: false });
readStream.on('error', onError);
readStream.on('end', function onReadEnd() {
concatInfo.concat++;
writeFiles(concatInfo, files, writeStream, onSuccess, onError);
});
} else {
writeStream.end();
onSuccess();
}
}
/**
* 放弃分片上传任务 - AWS.S3.abortMultipartUpload.
*/
function abortMultipartUpload(req, resp) {
let bucket = req.params.bucket;
let fileName = req.params.fileName;
let uploadId = req.query.uploadId;
// 清除上传信息
clearUploadInfo(bucket, uploadId);
resp.send('');
}
/**
* 上传分片数据 - AWS.S3.uploadPart
* 非分片上传 - AWS.S3.putObject
*/
function uploadPartOrPutObject(req, resp) {
let bucket = req.params.bucket;
let fileName = req.params.fileName;
let uploadId = req.query.uploadId;
let partNumber = req.query.partNumber;
let dataEnded = false;
let size = 0;
let spark = new SparkMD5.ArrayBuffer();
if (uploadId && partNumber) {
// 上传分片 - AWS.S3.uploadPart
let dataPath = path.join(IMAGE_UPLOAD_PART_DATA, uploadId);
fs.mkdirpSync(dataPath);
let dataFilePath = path.join(dataPath, partNumber + '.data');
let writeStream = fs.createWriteStream(dataFilePath);
req.pipe(writeStream);
// req.setTimeout(5000);
req.on("data", (data) => {
// 数据写入分片文件
// writeStream.write(data)
size += data.length;
spark.append(data);
}).on("end", () => {
// 数据接收完毕
dataEnded = true;
// writeStream.close();
let etag = spark.end();
resp.header('etag', etag);
resp.send('');
// 记录分片完成信息
let partInfo = {
PartNumber: partNumber,
ETag: etag,
Size: size
};
// 保存分片信息
addPartInfo(bucket, fileName, uploadId, partInfo);
}).on("close", () => {
if (!dataEnded) {
// 连接关闭但数未收完 - 浏览器刷新或其他问题导致数据未发完
logger.warn(`remove part file(req closed data not complete) ${bucket}-${fileName}-${partNumber}`);
try {
writeStream.close();
// fs.removeSync(dataFilePath);
} catch (err) {
logger.error(`req.close - error remove incomplete part file ${bucket}-${fileName}-${partNumber}`, err.stack);
}
}
}).on("error", (err) => {
logger.warn(`error on upload part ${bucket}-${fileName}-${partNumber}`, err.stack);
try {
writeStream.close();
// fs.removeSync(dataFilePath);
} catch (err) {
logger.error(`req.error - error remove incomplete part file ${bucket}-${fileName}-${partNumber}`, err.stack);
}
resp.write(e.stack);
resp.sendStatus(500);
});
} else {
// 小文件直接上传 - AWS.S3.putObject
let filePath = path.join(IMAGE_UPLOAD_FILE_DATA, bucket);
fs.mkdirpSync(filePath);
let writeStream = fs.createWriteStream(path.join(filePath, fileName));
req.pipe(writeStream);
req.on("data", (data) => {
// 数据写入文件
// writeStream.write(data)
size += data.length;
spark.append(data);
}).on("end", () => {
// writeStream.close();
let etag = spark.end();
resp.header('etag', etag);
resp.send('');
}).on("error", (err) => {
writeStream.close();
logger.error('error on put object', err.stack);
resp.write(e.stack);
resp.sendStatus(500);
});
}
}
/**
* 查询桶上传任务列表 - AWS.S3.listMulitpartUploads
*/
function listMultipartUploads(req, resp) {
let bucket = req.params.bucket;
let isListMultipartUploads = req.query.hasOwnProperty('uploads');
if (isListMultipartUploads) {
// 获取桶上传任务列表
let uploads = getBucketUploads(bucket);
let content = {
Bucket: bucket,
Upload: uploads
};
resp.send(js2xmlparser.parse('listMultipartUploadsResult', content));
} else {
// 非上传相关接口
resp.send('');
}
};
/**
* 清除上传信息.
*
* @param {*} bucket
* @param {*} uploadId
*/
function clearUploadInfo(bucket, uploadId) {
// 清空分片目录
clearPartDataDirectory(uploadId);
// 清除桶上传任务信息
clearUploadTask(bucket, uploadId);
}
// JSON保存格式
let jsonOption = {
spaces: 2,
EOL: '\r\n'
};
/**
* 初始化桶信息任务信息.
*/
function initBucketTaskInfoData(bucket) {
let bucketTaskInfo = {
Bucket: bucket,
Uploads: []
};
writeBucketTaskInfo2Disk(bucket, bucketTaskInfo);
return bucketTaskInfo;
}
/**
* 添加上传任务.
*
* 文件结构如下:
* {
* Bucket: 'bucketName1',
* Uploads: [{
* Key: 'fileName1',
* UploadId: 'uploadId1',
* Initiated: 'init time'
* }, {
* Key: 'fileName2',
* UploadId: 'uploadId2',
* Initiated: 'init time'
* }]
* }
*/
function addUploadTask(bucket, uploadInfo) {
let bucketTaskInfo = getBucketTaskInfo(bucket);
bucketTaskInfo.Uploads.push(uploadInfo);
writeBucketTaskInfo2Disk(bucket, bucketTaskInfo);
}
/**
* 获取桶任务信息 - 如果不存在则初始化并返回.
*/
function getBucketTaskInfo(bucket) {
let bucketTaskInfo = readBucketTaskInfoFromDisk(bucket);
if (!bucketTaskInfo) {
// 初始化桶信息
bucketTaskInfo = initBucketTaskInfoData(bucket);
}
return bucketTaskInfo;
}
/**
* 获取桶上传任务列表.
*/
function getBucketUploads(bucket) {
return getBucketTaskInfo(bucket).Uploads;
}
/**
* 清除桶上传任务信息.
*/
function clearUploadTask(bucket, uploadId) {
// 上传任务集 - 清除任务信息
let bucketTaskInfo = getBucketTaskInfo(bucket);
let uploads = bucketTaskInfo.Uploads || [];
for (let i = uploads.length - 1; i >= 0; i--) {
let upload = uploads[i];
if (upload.UploadId === uploadId) {
uploads.splice(i);
}
}
if (uploads.length > 0) {
writeBucketTaskInfo2Disk(bucket, bucketTaskInfo);
} else {
// 该桶内无上传任务 - 磁盘删除任务文件
removeBucketTaskInfoFromDisk(bucket);
}
}
/**
* 初始化上传分片信息.
*/
function initPartInfoData(bucket, fileName, uploadId) {
let partInfoData = {
Bucket: bucket,
Key: fileName,
Parts: []
};
writePartInfo2Disk(uploadId, partInfoData);
return partInfoData;
}
/**
* 添加分片信息.
*
* 文件结构
* {
* Bucket: 'bucketName1',
* Key: 'fileName1',
* Parts: [{
* PartNumber: 1,
* ETag: 'etag',
* Size: '200'
* }, {
* PartNumber: 2,
* ETag: 'etag',
* Size: '201'
* }]
* }
*/
function addPartInfo(bucket, fileName, uploadId, partInfo) {
let partInfoData = getPartInfoData(bucket, fileName, uploadId);
partInfoData.Parts.push(partInfo);
writePartInfo2Disk(uploadId, partInfoData);
}
/**
* 获取上传分片信息 - 如果不存在则初始化并返回.
*/
function getPartInfoData(bucket, fileName, uploadId) {
// 添加分片上传任务
let uploads = getBucketUploads(bucket);
// 检查当前uploadId是否有上传记录 - 无则添加
let hasRecord = false;
uploads.forEach((uploadTask) => {
if (uploadTask.UploadId === uploadId) {
hasRecord = true;
}
});
if (!hasRecord) {
let uploadInfo = {
Key: fileName,
UploadId: uploadId,
Initiated: (new Date()).getTime()
};
// 保存上传任务
addUploadTask(bucket, uploadInfo);
}
let partInfoData = readPartInfoFromDisk(uploadId);
if (!partInfoData) {
// 初始化上传分片信息
partInfoData = initPartInfoData(bucket, fileName, uploadId);
}
return partInfoData;
}
/**
* 清空分片目录.
*/
function clearPartDataDirectory(uploadId) {
let dataPath = path.join(IMAGE_UPLOAD_PART_DATA, uploadId);
// 清空文件分片目录
fs.emptyDir(dataPath).then(() => {
fs.rmdir(dataPath).catch(err => {
logger.error('error delete part data directory', err.stack);
});
}).catch(err => {
logger.error('error empty part data directory', err.stack);
});
// 删除分片信息文件
removePartInfoFromDisk(uploadId);
}
/**
* 磁盘读取桶任务信息.
*/
function readBucketTaskInfoFromDisk(bucket) {
let bucketTaskInfoFilePath = path.join(IMAGE_UPLOAD_DIR, `${bucket}-task.json`);
let bucketTaskInfo = null;
try {
if (fs.existsSync(bucketTaskInfoFilePath)) {
bucketTaskInfo = jsonfile.readFileSync(bucketTaskInfoFilePath);
}
} catch (err) {
logger.error('error read bucket task info from disk', err.stack);
}
return bucketTaskInfo;
}
/**
* 保存桶任务信息到磁盘.
*/
function writeBucketTaskInfo2Disk(bucket, bucketTaskInfo) {
let bucketTaskInfoFilePath = path.join(IMAGE_UPLOAD_DIR, `${bucket}-task.json`);
try {
jsonfile.writeFileSync(bucketTaskInfoFilePath, bucketTaskInfo, jsonOption);
} catch (err) {
logger.error('error writing bucket task info to disk', err.stack);
}
}
/**
* 磁盘删除桶任务信息.
*/
function removeBucketTaskInfoFromDisk(bucket) {
let bucketTaskInfoPath = path.join(IMAGE_UPLOAD_DIR, `${bucket}-task.json`);
try {
fs.removeSync(bucketTaskInfoPath);
} catch (err) {
logger.error('error remove bucket info from disk', err.stack);
}
}
/**
* 磁盘读取分片信息.
*/
function readPartInfoFromDisk(uploadId) {
let partInfoFilePath = path.join(IMAGE_UPLOAD_PART_DATA, `${uploadId}.json`);
let partInfoData = null;
// 磁盘读取分片信息
try {
if (fs.existsSync(partInfoFilePath)) {
partInfoData = jsonfile.readFileSync(partInfoFilePath);
}
} catch (err) {
logger.error('error read part info from disk', err.stack);
}
return partInfoData;
}
/**
* 保存分片信息到磁盘.
*/
function writePartInfo2Disk(uploadId, partInfoData) {
let partInfoFilePath = path.join(IMAGE_UPLOAD_PART_DATA, `${uploadId}.json`);
try {
jsonfile.writeFileSync(partInfoFilePath, partInfoData, jsonOption);
} catch (err) {
logger.error('error writing part info to disk', err.stack);
}
}
/**
* 磁盘删除分片信息.
*/
function removePartInfoFromDisk(uploadId) {
let partInfoFilePath = path.join(IMAGE_UPLOAD_PART_DATA, `${uploadId}.json`);
try {
fs.removeSync(partInfoFilePath);
} catch (err) {
logger.error('error remove part info from disk', err.stack);
}
}
/**
* 配置文件数据目录双向信息校对.
*/
function validConfigData() {
// TODO 配置文件、数据目录双向信息校对
logger.info('validConfigData');
// 这个步骤是必要的-考虑服务端宕机数据文件部分写入的情况 - 比较常见(正在上传ctrl+C关闭服务端城乡)
// TODO 是否要考虑配置文件部分写入的情况?概率较小暂时可以忽略
}
/**
* 保存镜像文件的virtual size信息
*/
function saveImageGlanceVirtualSize(req, resp) {
return new Promise(function(resolve, reject) {
const Consts = require('../../constants');
const request = require('request');
const session = req.session;
const id = req.params.id;
const bucketName = req.body.bucketName;
const fileName = req.body.fileName;
const platform = os.platform();
const filePath = `${IMAGE_UPLOAD_FILE_DATA}/${bucketName}/${fileName}`;
let cmd = '';
if (platform === 'linux') {
cmd = 'exec /usr/bin/qemu-img';
} else if (platform === 'win32') {
cmd = path.resolve(__dirname, '..\\..\\lib\\qemu-img\\win32\\qemu-img.exe');
}
cmd = `${cmd} info ${filePath}`;
exec(cmd, function (error, stdout, stderr) {
if (error) {
logger.error(error);
resolve();
} else {
let reg = /virtual size: .* \((\d*) bytes\)/;
let matches = stdout.match(reg);
if (matches === null) {
logger.error('cmd get qemu-img info cannot match virtual size.');
resolve();
} else {
let endpoint = session[Consts.KEY_SERVICES]['glance'];
let options = {
url: `${endpoint}/v2/images/${id}`,
method: 'patch',
body: JSON.stringify([{
"op": "add",
"path": "/inspur_virtual_size",
"value": `${matches[1]}`
}]),
headers: {
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
request(options, (error, response, body) => {
console.log('error:', error);
console.log('body:', body);
resolve();
});
}
}
});
});
}
/**
* 保存至Glance后端与s3无关保存后文件删除
*/
async function saveToGlance(req, resp) {
const Consts = require('../../constants');
const request = require('request');
const session = req.session;
await saveImageGlanceVirtualSize(req); // 获取并保存virtual size
const id = req.params.id;
const uploadId = req.body.uploadId;
const bucketName = req.body.bucketName;
const fileName = req.body.fileName;
let endpoint = session[Consts.KEY_SERVICES]['glance'];
let options = {
url: `${endpoint}/v2/images/${id}/file`,
method: Consts.PUT,
headers: {
'Content-type': 'application/octet-stream',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
const filePath = `${IMAGE_UPLOAD_FILE_DATA}/${bucketName}/${fileName}`;
let readStream = fs.createReadStream(filePath);
let httpreq = request(options, function(error, response, body) {
if (!error && (response.statusCode === 200 || response.statusCode === 204)) {
logger.info(`Save to glance successfully. Image Id: ${id}`);
} else {
logger.warn(`Save to glance failed. Image Id: ${id}`);
}
});
readStream.pipe(httpreq);
readStream.on('end', function() {
fs.unlink(filePath, function(err) {
if (err) {
logger.warn('Unlink ' + filePath + ' Failed');
} else {
logger.info('Unlink ' + filePath + ' Success');
}
});
if (uploadId !== undefined) {
clearUploadInfo(bucketName, uploadId);
}
});
resp.json({
[Consts.KEY_FLAG]: true
});
}
validConfigData();
module.exports = {
setResType,
listParts,
createOrCompleteMultipartUpload,
abortMultipartUpload,
uploadPartOrPutObject,
listMultipartUploads,
getFileConcatProgress,
getAllFileUploadProgress,
saveToGlance
};

View File

@ -0,0 +1,55 @@
module.exports = function(options, context) {
let express = require('express');
let expressXmlParser = require('express-xml-parser');
let bodyParser = require('body-parser');
let router = express.Router();
const controller = require('./controller');
let xmlBodyParser = expressXmlParser({
type: ['application/*'],
explicitArray: false,
normalize: false,
normalizeTags: false,
trim: true
});
router.post('/save-to-glance/:id', controller.saveToGlance);
router.all('/*', controller.setResType);
/**
* 查询上传任务列表 - AWS.S3.listMulitpartUploads
*/
router.get('/:bucket/', controller.listMultipartUploads);
/**
* 查询桶内所有文件上传进度 - 非AWS API.
*/
router.get('/:bucket/all-file/upload-progress', controller.getAllFileUploadProgress);
/**
* 查询已上传分片信息 - AWS.S3.listParts.
*/
router.get('/:bucket/:fileName', controller.listParts);
/**
* 创建分片上传任务 - AWS.S3.createMultipartUpload
* 完成分片上传 - AWS.S3.completeMultipartUpload.
*/
router.post('/:bucket/:fileName', xmlBodyParser, controller.createOrCompleteMultipartUpload);
/**
* 上传分片数据 - AWS.S3.uploadPart
* 非分片上传 - AWS.S3.putObject
*/
router.put('/:bucket/:fileName', controller.uploadPartOrPutObject);
/**
* 放弃分片上传任务 - AWS.S3.abortMultipartUpload.
*/
router.delete('/:bucket/:fileName', controller.abortMultipartUpload);
router.get('/:bucket/:fileName/concat-progress', controller.getFileConcatProgress);
return router;
};

View File

@ -0,0 +1,14 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('GetServiceAddr');
const router = require('express').Router();
//constants
let serviceAddr = context.getResource('serviceAddr.json');
///^(\/login\/|\/index\/)*/
router.all('/', function(req, res) {
res.json(serviceAddr);
});
return router;
};

View File

@ -0,0 +1,48 @@
module.exports = function(options, context) {
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../constants');
const request = require('request');
router.post('/', function(req, res) {
let session = req.session;
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let ip = req.headers["x-forwarded-for"] ||
req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress || '';
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
req.body.user.create_time = time;
let options = {
url: `${url}/v3/inspur/users/${req.body.userId}/password`,
method: Consts.POST,
body: {
user: req.body.user
},
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': '',
"remote-ip": ip,
"region-id": ''
}
};
res.header('Cache-Control', 'no-cache');
request(options, function(error, response, body) {
if (body && body.error) {
res.status(body.error.code);
}
res.json(body);
});
});
return router;
};

View File

@ -0,0 +1,60 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('RecordLog');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function(req, res) {
let session = req.session;
let body = req.body;
let baseUrl = req.baseUrl;
let restUrl = "/v3/inspur/logs";
if (!baseUrl.startsWith('/')) {
baseUrl = '/' + baseUrl;
}
//let moduleName = baseUrl.split('/')[3];
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
//let host = session[Consts.KEY_SERVICES][moduleName];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
body.logs.forEach((item, index) => {
item.ip = session[Consts.KEY_IP];
item.create_time = time;
})
let options = {
url: url+restUrl,
method: Consts.POST,
json: true,
body: body,
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
"remote-ip":session[Consts.KEY_IP],
//'X-Auth-Token': session[Consts.PROJECT_TOKEN] || "",
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
request(options, function(error, response, body) {
if (!error && response.statusCode == 201) {
res.json(body);
} else {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: body?(body.error?body.error.code:""):"",
[Consts.KEY_RESDATA]: ''
});
}
});
});
return router;
};

View File

@ -0,0 +1,62 @@
module.exports = function (options, context) {
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function (req, res) {
let username = req.body.username;
let password = req.body.password;
if ('userName' in req.session) {
if (req.session.userName !== username) {
res.status(403);
res.json({});
}
}
let serviceAddr = context.getResource('serviceAddr.json');
let host = urlApi.format({
protocol: Consts.HTTP,
host: serviceAddr['keystone']
});
let options = {
url: `${host}/v3/auth/tokens`,
method: Consts.POST,
json: true,
body: {
'auth': {
'identity': {
'methods': [
'password'
],
'password': {
'user': {
'name': username,
'domain': {
'name': 'default'
},
'password': password
}
}
}
}
},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT
}
};
request(options, function (error, response, body) {
if (error || body.error) {
if (JSON.stringify(body).indexOf('The account is locked for user') > -1) {
res.status(401);
} else {
res.status(403);
}
}
res.json(body);
});
});
return router;
};

View File

@ -0,0 +1,16 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('GetToken');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.get('/', function(req, res) {
let session = req.session;
res.json({
token:session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN]?session[Consts.KEY_TOKEN]:"")
});
});
return router;
};

View File

@ -0,0 +1,91 @@
const logger = require('log4js').getLogger('GenVerifyCode');
const router = require('express').Router();
const BMP24 = require('gd-bmp').BMP24;
const crypto =require('crypto');
//Consts
const Consts = require('../../constants');
const request = require('request');
const SessionHelper = require('../../lib/SessionHelper');
//仿PHP的rand函数
function rand(min, max) {
let ran = crypto.randomBytes(1)[0];
ran = ran.toString().substr(-1)/10;
return ran*(max-min+1) + min | 0; //特殊的技巧,|0可以强制转换为整数
}
//制造验证码图片
function makeCapcha() {
var img = new BMP24(100, 40);
// img.drawCircle(rand(0, 100), rand(0, 40), rand(10 , 40), rand(0, 0xffffff));
//边框
// img.drawRect(0, 0, img.w-1, img.h-1, rand(0, 0xffffff));
// img.fillRect(rand(0, 100), rand(0, 40), rand(10, 35), rand(10, 35), rand(0, 0xffffff));
//return img;
// img.fillRect(rand(0, 100), rand(0, 40), rand(10, 35), rand(10, 35), 0xffffff);
img.fillRect(0,0,100,40, 0xffffff); //bg
for(var i=0; i<155; i++){
img.drawLine(rand(0, 100), rand(0, 40), rand(0, 100), rand(0, 40), 0xd7e2d1);
}
//画曲线
var w=img.w/2;
var h=img.h;
var color = rand(0, 0xffffff);
var y1=rand(-5,5); //Y轴位置调整
var w2=rand(10,15); //数值越小频率越高
var h3=rand(4,6); //数值越小幅度越大
var bl = rand(1,5);
for(var i=-w; i<w; i+=0.1) {
var y = Math.floor(h/h3*Math.sin(i/w2)+h/2+y1);
var x = Math.floor(i+w);
for(var j=0; j<bl; j++){
img.drawPoint(x, y+j, color);
}
}
// var p = "ABCDEFGHKMNPQRSTUVWXYZ3456789";
var p = "123456789";
var str = '';
for(var i=0; i<4; i++){
let ran = crypto.randomBytes(1)[0];
ran = ran.toString().substr(-1)/10;
str += p.charAt(ran * p.length |0);
}
var fonts = [BMP24.font8x16, BMP24.font12x24, BMP24.font16x32];
var fonts = [BMP24.font16x32];
// var x = 15, y=8;
var x = 12, y=8;
for(var i=0; i<str.length; i++){
let ran = crypto.randomBytes(1)[0];
ran = ran.toString().substr(-1)/10;
var f = fonts[ran * fonts.length |0];
// y = 8 + rand(-10, 10);
y = 4;
// img.drawChar(str[i], x, y, f, rand(0, 0xffffff));
img.drawChar(str[i], x, y, f, rand(200, 0xffffff));
x += f.w + rand(2, 8);
}
return {
verifyCode: str,
img: img
}
}
module.exports = function(options, context) {
router.all('/', function(req, res) {
let body = req.body;
let query = req.query;
let type = "verifyCode";
query.type && (type = query.type);
let verifyResult = makeCapcha();
SessionHelper.setVerifyCode(req, type, verifyResult.verifyCode);
res.setHeader('Content-Type', 'image/bmp');
res.header("Cache-Control", "no-cache");
res.end(verifyResult.img.getFileData() );
});
return router;
};

View File

@ -0,0 +1,21 @@
module.exports = function(options, context) {
const mysql = require('promise-mysql');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.get('/', function(req, res) {
req.session.showVerifyCode = false;
res.header("Cache-Control","no-cache");
res.json({
verification_code: {
config_key: "verificationCode",
config_value: "0"
}
});
});
return router;
};

View File

@ -0,0 +1,416 @@
module.exports = function (options, context) {
const mysql = require('promise-mysql');
const logger = require('log4js').getLogger('Login');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
const moment = require('moment');
const crypto = require('crypto');
let serviceAddr = context.getResource('serviceAddr.json');
function getShowVerFun(host) {
return new Promise((resolve, reject) => {
let o = {
url: host + "/v3/inspur/system/config/verification",
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT
}
};
request(o, function (error, response, body) {
if (error) {
reject(error);
} else {
if (!body.error) {
resolve(body.hasOwnProperty('verification_code') && body.verification_code.hasOwnProperty('config_value') && body.verification_code.config_value === 1);
}
}
});
});
}
async function getIPContinuousWrong(ip) {
try {
const connection = await mysql.createConnection({
host: serviceAddr.mysql.host,
port: serviceAddr.mysql.port,
user: serviceAddr.mysql.user,
password: serviceAddr.mysql.password,
database: serviceAddr.mysql.database
});
let result = false;
let results = await connection.query(`SELECT * FROM login_ip_logs WHERE ip = ?`, [ip]);
if (results.length > 0) {
if (results[0].continuous_wrong_numbers > 0) {
result = true;
}
} else {
result = false;
}
await connection.end();
return result;
} catch (e) {
throw e;
}
}
async function setIPContinuousWrongNumber(ip, success) {
try {
const connection = await mysql.createConnection({
host: serviceAddr.mysql.host,
port: serviceAddr.mysql.port,
user: serviceAddr.mysql.user,
password: serviceAddr.mysql.password,
database: serviceAddr.mysql.database
});
let existingResults = await connection.query(`SELECT * FROM login_ip_logs WHERE ip = ?`, [ip]);
if (existingResults.length > 0) {
if (success) {
await connection.query(`UPDATE login_ip_logs SET continuous_wrong_numbers = ? WHERE id = ?`, [0, existingResults[0].id]);
} else {
await connection.query(`UPDATE login_ip_logs SET continuous_wrong_numbers = continuous_wrong_numbers + 1 WHERE id = ?`, [existingResults[0].id]);
}
} else {
if (success) {
await connection.query(`INSERT INTO login_ip_logs (ip, continuous_wrong_numbers, update_time) VALUES (?, ?, ?)`, [ip, 0, new Date()]);
} else {
await connection.query(`INSERT INTO login_ip_logs (ip, continuous_wrong_numbers, update_time) VALUES (?, ?, ?)`, [ip, 1, new Date()]);
}
}
await connection.end();
return true;
} catch (e) {
throw e;
}
}
function getSessionCount(req) {
return new Promise((resolve, reject) => {
req.sessionStore.all((error, obj) => {
let sessionCount = 0;
for (let key in obj) {
let item = obj[key];
if (typeof item === 'string') {
item = JSON.parse(item);
}
if ('userId' in item) {
sessionCount += 1;
}
}
resolve(sessionCount);
});
});
}
function getSystemConfig(host) {
return new Promise((resolve, reject) => {
let opt = {
url: `${host}/v3/inspur/system/config/before-auth`,
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': ''
}
};
request(opt, (error, response, body) => {
if (body instanceof Array) {
let systemConfig = {};
for (let i = 0; i < body.length; i++) {
systemConfig[body[i][0]] = String(body[i][1]);
}
resolve(systemConfig);
} else {
resolve({});
}
});
});
}
function addCaptchaToSession(req) {
var p = "123456789";
var newCaptcha = '';
for (var i = 0; i < 4; i++) {
let ran = crypto.randomBytes(1)[0];
ran = ran.toString().substr(-1) / 10;
newCaptcha += p.charAt(ran * p.length | 0);
}
req.session['verifyCode'] = newCaptcha;
}
function loginOrigin({host, authObj, ip, traceId}) {
return new Promise((resolve, reject) => {
let opt = {
url: `${host}/v3/auth/tokens`,
method: Consts.POST,
json: true,
body: authObj,
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Openstack-Request-Id': traceId,
"remote-ip": ip
}
};
request(opt, (error, response, body) => {
if (error) {
reject(body);
} else if (body.error) {
reject(body);
} else {
resolve(response);
}
});
});
}
function sendSMS({host, token, ip, traceId}) {
return new Promise((resolve, reject) => {
let opt = {
url: `${host}/v3/inspur/auth/sms`,
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Openstack-Request-Id': traceId,
"remote-ip": ip,
'X-Auth-Token': token
}
};
request(opt, (error, response, body) => {
if (error) {
reject(body);
} else if (body.error) {
reject(body);
} else {
resolve(response);
}
});
});
}
function login({host, authObj, ip, traceId, headers={}}) {
return new Promise((resolve, reject) => {
let opt = {
url: `${host}/v3/inspur/auth/tokens`,
method: Consts.POST,
json: true,
body: authObj,
qs: {},
headers: {
...headers,
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Openstack-Request-Id': traceId,
"remote-ip": ip
}
};
request(opt, (error, response, body) => {
if (error) {
reject(body);
} else if (body.error) {
reject(body);
} else {
resolve(response);
}
});
});
}
function getSessionConfig({host, token}) {
return new Promise((resolve, reject) => {
let opt = {
url: `${host}/v3/inspur/system/config/session`,
method: Consts.GET,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': token
}
};
request(opt, (error, response, body) => {
if (error) {
reject(body);
} if (body.error) {
reject(body.error);
} else {
let config = {
sessionTimeout: '10',
multiSession: false
};
if ('session_timeout' in body) {
config.sessionTimeout = body.session_timeout.config_value;
}
if ('multi_session' in body) {
config.multiSession = parseInt(body.multi_session.config_value) === 1;
}
resolve(config);
}
});
});
}
router.post('/', async function (req, res) {
let traceId = req.headers['x-openstack-request-id'] || ''; // 获取追踪id
try {
if (serviceAddr.twoFactorAuth) {
let { twoFactorAuthSwitch = false, twoFactorAuthSolution = '' } = serviceAddr.twoFactorAuth;
if (twoFactorAuthSwitch && twoFactorAuthSolution === 'inspur_cert_v3') {
let sslClientCN = req.headers['x-ssl-client-cn'] || '';
let inUserName = req.body.auth.identity.password.user.name || '';
if (sslClientCN !== inUserName) {
res.status(401).json({ error: 'user name does not match ssl cn.' });
return;
}
}
}
} catch (e) {
console.error(e);
}
let host = urlApi.format({
protocol: Consts.HTTP,
host: serviceAddr['keystone']
});
let systemConfig = await getSystemConfig(host);
let showVerifyCode = false; // await getShowVerFun(host);
//校验验证码
let flg = true;
let showVer = req.cookies.showVerifycode;
let session = req.session;
let code = req.body.verifyCode;
let verifyCode = session["verifyCode"];
let ip = req.headers["x-forwarded-for"] || req.ip || req.connection.remoteAddress || req.socket.remoteAddress || '';
const isIPContinuousWrong = false; // await getIPContinuousWrong(ip);
if (systemConfig.sms_valid === '1' && 'validcode' in req.headers) { // 如果要求短信验证码,且有短信验证码,则认为是已经完成了账号密码验证,此时不需要验证码
flg = true;
} else if (showVer || session.showVerifyCode || showVerifyCode || isIPContinuousWrong) { //显示验证码
if (!verifyCode || typeof verifyCode === 'undefined') { //过期
flg = false;
}
if (!code || typeof code === 'undefined') { //没传递过来验证码
flg = false;
}
if (code !== undefined && verifyCode.toLocaleLowerCase() !== code.toLocaleLowerCase()) {
flg = false;
}
} else { //设置不显示验证码
if (code) { //这种情况是输错密码导致出现的验证码
if (verifyCode.toLocaleLowerCase() !== code.toLocaleLowerCase()) {
flg = false;
}
}
}
if (!flg) { //失败
setIPContinuousWrongNumber(ip, false);
res.json({ error: "verifyCode fail" });
return;
}
let sessionCount = 0; // await getSessionCount(req);
if ('max_session' in systemConfig) {
let maxSession = parseInt(systemConfig.max_session);
if (maxSession !== -1 && sessionCount >= systemConfig.max_session) {
// 超过最大会话数
if (/^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$/.test(systemConfig.max_session_time)) {
const times = systemConfig.max_session_time.split('-');
const now = moment();
const startTime = moment(now);
startTime.hour(times[0].split(':')[0]);
startTime.minute(times[0].split(':')[1]);
const endTime = moment(now);
endTime.hour(times[1].split(':')[0]);
endTime.minute(times[1].split(':')[1]);
if ((startTime.isAfter(endTime) && !now.isBetween(endTime, startTime)) || (startTime.isBefore(endTime) && now.isBetween(startTime, endTime)) || (startTime.isSame(endTime) && now.isSame(startTime))) {
res.status(401);
res.json({
error: {
code: 401,
message: 'Fail: max session reached.'
}
});
return;
}
}
}
}
addCaptchaToSession(req);
delete req.body.verifyCode;
if (ip.split(',').length > 0) {
ip = ip.split(',')[0];
}
req.body.auth.remoteip = ip;
let date = new Date();
let time = date.getTime() + date.getTimezoneOffset() * 60 * 1000;
req.body.auth.create_time = time;
try {
if (systemConfig.sms_valid === '1' && !('validcode' in req.headers)) { // 如果要求短信验证码,且请求头中没有该值,则执行发送短信
let loginOriginRes = await loginOrigin({ host, authObj: req.body, ip, traceId });
setIPContinuousWrongNumber(ip, true);
let token = loginOriginRes.headers['x-subject-token'];
let sendSMSRes = await sendSMS({ host, token, ip, traceId });
res.json({
phoneNumber: sendSMSRes.body.toString()
});
} else {
headers = {};
if (systemConfig.sms_valid === '1') {
if ('validcode' in req.headers) {
headers['VALIDCODE'] = req.headers['validcode'];
}
}
let loginRes = await loginOrigin({ host, authObj: req.body, ip, traceId, headers });
req.session['loginTraceId'] = traceId;
setIPContinuousWrongNumber(ip, true);
let resBody = loginRes.body;
let token = loginRes.headers['x-subject-token'];
let sessionConfig = {
multiSession: true
};// await getSessionConfig({ host, token });
if (!sessionConfig.multiSession) {
let dev = process.env.NODE_ENV === 'development';
if (dev) {
for (var key in req.sessionStore.sessions) {
var item = req.sessionStore.sessions[key];
if (JSON.parse(item).userId === userId) {
delete req.sessionStore.sessions[key];
}
}
} else {
req.sessionStore.all(function (error, obj) {
for (var key in obj) {
if (obj[key].userId === userId) {
req.sessionStore.destroy(key, function () {});
}
}
});
}
}
resBody.ip = ip;
resBody.tokenId = token;
resBody.sessionTimeout = sessionConfig.sessionTimeout;
res.status(200).json(resBody);
}
} catch (e) {
setIPContinuousWrongNumber(ip, false);
res.status(401).send(e);
}
});
return router;
};

View File

@ -0,0 +1,67 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('Logout');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function(req, res) {
let traceId = req.headers['x-openstack-request-id'] || ''; // 获取追踪id
let session = req.session;
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+"/v3/auth/tokens",
method: Consts.DELETE,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Openstack-Request-Id': traceId,
"remote-ip": session[Consts.KEY_IP],
'host': host || "",
'X-Subject-Token': session[Consts.KEY_TOKEN]||"",
'X-Auth-Token': session[Consts.KEY_TOKEN]||""
}
};
request(options, function(error, response, body) {
if (!error) {
// 备注:这里用的 session-file-store 在destroy 方法里并没有销毁cookie
// 所以客户端的 cookie 还是存在,导致的问题 --> 退出登陆后服务端检测到cookie
// 然后去查找对应的 session 文件,报错
// session-file-store 本身的bug
req.session.destroy(function(err) {
if (err) {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'logout failed',
[Consts.KEY_RESDATA]: ''
});
return;
}
// req.session.loginUser = null;
res.clearCookie(Consts.IDENTITY_KEY);
res.clearCookie('connect.sid');
// return res.redirect('/login');
res.json({
[Consts.KEY_FLAG]: true,
[Consts.KEY_RESULT]: Consts.RESULT_SUCCESS
});
});
} else {
res.status(response.statusCode);
res.json(error);
}
});
});
return router;
};

View File

@ -0,0 +1,65 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('resetPassword');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function(req, res) {
let traceId = req.headers['x-openstack-request-id'] || ''; // 获取追踪id
let session = req.session;
let baseUrl = req.baseUrl;
let restUrl = "/v3/inspur/users/"+req.body.user.userId+"/password";
if (!baseUrl.startsWith('/')) {
baseUrl = '/' + baseUrl;
}
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let ip = req.headers["x-forwarded-for"]||
req.ip||
req.connection.remoteAddress||
req.socket.remoteAddress||'';
if (ip.split(',').length>0) {
ip = ip.split(',')[0];
}
req.body.user.remote_ip = ip;
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
req.body.user.create_time = time;
let options = {
url: url+restUrl,
method: Consts.POST,
json: true,
body: req.body,
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
"remote-ip":session[Consts.KEY_IP],
'host':host || "",
"region-id":req.cookies.region_id||"",
'X-Auth-Token':session[Consts.PROJECT_TOKEN]||session[Consts.KEY_TOKEN]||"",
'X-Openstack-Request-Id': traceId
}
};
request(options, function(error, response, body) {
if(body){
if (!body.error) {
res.json(body);
} else {
res.status(body.error.code);
res.json(body);
}
} else {
res.json({});
}
});
});
return router;
};

View File

@ -0,0 +1,40 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('SaveLoginInfoAction');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const SessionHelper = require('../../lib/SessionHelper');
router.use(function(req, res, next) {
next();
});
router.all('/', function(req, res) {
req.session.regenerate(function(err) {
if (err) {
res.jsonp({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'logout failed',
[Consts.KEY_RESDATA]: ''
});
return;
}
SessionHelper.setSession(req, res, function () {
res.jsonp({
[Consts.KEY_FLAG]: true,
[Consts.KEY_RESULT]: Consts.RESULT_SUCCESS
});
});
//调取权限接口获取权限列表存入session
// SessionHelper.setPrivilege(req, res, function() {
// res.jsonp({
// [Consts.KEY_FLAG]: true,
// [Consts.KEY_RESULT]: Consts.RESULT_SUCCESS
// });
// });
});
});
return router;
};

View File

@ -0,0 +1,168 @@
module.exports = function (options, context) {
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
const crypto = require('crypto');
let serviceAddr = context.getResource('serviceAddr.json');
let encryptAlgorithm = serviceAddr.encryptAlgorithm || 'aes-256-cbc';
let encryptKey = serviceAddr.encryptKey || '0123456789abcdef0123456789abcdef';
let encryptIV = serviceAddr.encryptIV || '0123456789abcdef';
let host = urlApi.format({
protocol: Consts.HTTP,
host: serviceAddr['keystone']
});
async function getPIMSettings(token) {
return new Promise((resolve, reject) => {
let options = {
url: `${host}/v3/inspur/customparam/pim_params`,
method: Consts.GET,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': token
}
};
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
if (body.params && body.params.length > 0) {
let params = JSON.parse(body.params[0].value);
resolve(params);
} else {
resolve({});
}
}
});
});
}
async function setPIMSettings(token, settings) {
return new Promise((resolve, reject) => {
let options = {
url: `${host}/v3/inspur/customparam/pim_params`,
method: Consts.POST,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': token
},
body: {
param: {
value: JSON.stringify(settings)
}
}
};
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
function encrypt(plainText) {
let cipher = crypto.createCipheriv(encryptAlgorithm, encryptKey, encryptIV);
let cipherText = cipher.update(plainText, 'utf8', 'base64');
cipherText = cipher.final('base64');
return cipherText;
}
function decrypt(cipherText) {
let decipher = crypto.createDecipheriv(encryptAlgorithm, encryptKey, encryptIV);
let plainText = decipher.update(cipherText, 'base64', 'utf8');
plainText = decipher.final('utf8');
return plainText;
}
function btoa(plainText) {
return Buffer.from(plainText).toString('base64');
}
router.get('/settings', async function(req, res) {
res.header('Cache-Control', 'no-cache');
try {
let settings = await getPIMSettings(req.session.ptoken);
if ('password' in settings) {
delete settings.password;
}
res.json(settings);
} catch (e) {
res.status(400);
res.json(e);
}
});
router.post('/settings', async function (req, res) {
res.header('Cache-Control', 'no-cache');
let encryptedPassword = '';
try {
if (!req.body.password || req.body.password === '') {
let existingPIMSettings = await getPIMSettings(req.session.ptoken);
if (existingPIMSettings.password && existingPIMSettings.password !== '') {
encryptedPassword = existingPIMSettings.password;
} else {
encryptedPassword = '';
}
} else {
encryptedPassword = encrypt(req.body.password);
}
} catch (e) {
encryptedPassword = '';
}
req.body.password = encryptedPassword;
try {
await setPIMSettings(req.session.ptoken, req.body);
res.json({});
} catch (e) {
res.status(400);
res.json(e);
}
});
router.post('/login', async function (req, res) {
res.header('Cache-Control', 'no-cache');
try {
let settings = await getPIMSettings(req.session.ptoken);
let username = settings.username || '';
let password = settings.password || '';
let ip = settings.ip || '';
let apiPort = settings.apiPort || 80;
password = decrypt(password);
let options = {
url: `http://${ip}:${apiPort}/v1/auth/login`,
method: Consts.POST,
json: true,
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT
},
body: {
username: username,
password: btoa(btoa(btoa(btoa(btoa(password)))))
}
};
request(options, function(error, response, body) {
if (error) {
res.status(400);
res.json(body);
} else {
res.json(body);
}
});
} catch (e) {
res.status(400);
res.json(e);
}
});
return router;
};

View File

@ -0,0 +1,78 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('ProjectToken');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function(req, res) {
let session = req.session;
let body = req.body;
let baseUrl = req.baseUrl;
let restUrl = "/v3/auth/tokens";
if (!baseUrl.startsWith('/')) {
baseUrl = '/' + baseUrl;
}
let moduleName = baseUrl.split('/')[3];
let host = session[Consts.KEY_SERVICES][moduleName];
let url = urlApi.format({
protocol: Consts.HTTP,
// hostname: '',
// port: 8080,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.KEY_TOKEN] || "",
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
let bodyParam = {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": session[Consts.KEY_USER_ID],
"password": session[Consts.KEY_PASSWORD]
}
}
},
"scope": {
"project": {
"id": body.pid
}
}
}
};
options.body = bodyParam;
request(options, function(error, response, body) {
let pToken = response.headers["x-subject-token"];
req.session[Consts.PROJECT_TOKEN] = pToken;
if (!error && response.statusCode == 201) {
res.json(body);
} else {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'reqTimeout',
[Consts.KEY_RESDATA]: ''
});
}
});
});
return router;
};

View File

@ -0,0 +1,78 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('Projects');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
router.post('/', function(req, res) {
let session = req.session;
let body = req.body;
let baseUrl = req.baseUrl;
let restUrl = "/v3/auth/tokens";
if (!baseUrl.startsWith('/')) {
baseUrl = '/' + baseUrl;
}
let moduleName = baseUrl.split('/')[3];
let host = session[Consts.KEY_SERVICES][moduleName];
let url = urlApi.format({
protocol: Consts.HTTP,
// hostname: '',
// port: 8080,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.KEY_TOKEN] || "",
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
let bodyParam = {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": session[Consts.KEY_USER_ID],
"password": session[Consts.KEY_PASSWORD]
}
}
},
"scope": {
"project": {
"id": body.pid
}
}
}
};
options.body = bodyParam;
request(options, function(error, response, body) {
let pToken = response.headers["x-subject-token"];
req.session[Consts.PROJECT_TOKEN] = pToken;
if (!error && response.statusCode == 201) {
res.json(body);
} else {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'reqTimeout',
[Consts.KEY_RESDATA]: ''
});
}
});
});
return router;
};

81
server/routes/s3Api.js Normal file
View File

@ -0,0 +1,81 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('S3Api');
var bodyParser = require('body-parser');
//Consts
const Consts = require('../constants');
var proxy = context.getResource('proxy');
var router = require('express').Router();
var AWS = require('aws-sdk');
var signer = require('aws-sdk/lib/signers/s3');
router.use(bodyParser.json())
.use(bodyParser.urlencoded({
extended: false
}))
.use("/:service", function(req, res) {
let service = req.params.service;
let session = req.session;
if (service === 'cacheAKSK') {
// 暂存ak/sk到session - 项目切换需要更新ak/sk
let ak = req.query.ak,
sk = req.query.sk;
// logger.warn('req.body', req.body);
session[Consts.S3AK] = ak;
session[Consts.S3SK] = sk;
session.save(function() {
res.status(200);
res.end();
});
// res.status(200);
// res.end();
} else if (service === 'rgw') {
// logger.warn('req.headers - before\n', req.headers);
let s3hostAddr = session[Consts.KEY_SERVICES]['S3'];
let reqDate = req.headers['x-amz-date'];
delete req.headers['x-amz-date'];
delete req.headers['authorization'];
let param2sign = {
method: req.method,
headers: [],
path: req.url
};
// lib/signer/s3 读取Content-Type字段
// 将content-type转为Content-Type - 首字母大写
if (req.headers['content-type']) {
param2sign.headers['Content-Type'] = req.headers['content-type'];
}
if (req.headers['content-md5']) {
param2sign.headers['Content-MD5'] = req.headers['content-md5'];
}
AWS.util.each(req.headers, function (name) {
if (name.match(/^x-amz-/i)) {
param2sign.headers[name] = req.headers[name];
}
});
let requestSigner = new signer(param2sign);
// 从session读取ak/sk
let ak = session[Consts.S3AK],
sk = session[Consts.S3SK];
// 为请求生成signatrue - req.headers中增加Authorization和X-Amz-Date字段
requestSigner.addAuthorization({
accessKeyId: ak,
secretAccessKey: sk
}, new Date(reqDate || new Date()));
// logger.warn('requestSigner.stringToSign()\n', requestSigner.stringToSign());
req.headers['x-amz-date'] = param2sign.headers['X-Amz-Date'];
req.headers['Authorization'] = param2sign.headers['Authorization'];
// logger.warn('req.headers - after\n', req.headers);
proxy.web(req, res, {
target: s3hostAddr
});
} else {
res.status(400);
res.write('not rgw related request, rgw request should use /s3-api/rgw or /s3-api/cacheAKSK');
res.end();
}
});
return router;
};

View File

@ -0,0 +1,211 @@
/**
* 安全基线需求.
* 48286 鉴权及访问控制
* 外部接口采用时间戳随机数等方案防止常见重放攻击常见重放类攻击主要包括各类网络管理协议的身份鉴别信息重放攻击等
*/
const logger = require('log4js').getLogger('Replay-Attack-Checker');
const Consts = require('../../constants');
const crypto = require('crypto');
module.exports = function(req, res, next) {
let reqIdentifier = getReqIdentifier(req);
if (reqIdentifier !== '') {
let decryptedReqIdentifier = '';
try {
// base64解码
let reqIdentifierBuffer = Buffer.from(reqIdentifier, 'base64');
// RSA私钥解密
decryptedReqIdentifier = crypto.privateDecrypt(privateKey, reqIdentifierBuffer).toString(STR_ENCODE);
} catch (error) {
// 请求标识解密失败的场景
logger.warn('Replay attack error', error.stack);
replayAttackDetected(req, res, 'invalide req idnetifier - decode & decrypt');
}
if (decryptedReqIdentifier) {
let seperatorIndex = decryptedReqIdentifier.indexOf('_');
if (seperatorIndex > -1) {
let ts = Number.parseInt(decryptedReqIdentifier.substring(0, seperatorIndex));
let identifier = decryptedReqIdentifier.substring(seperatorIndex + 1);
if (ts && identifier) {
if (!isMaxTimeDiffExceeded(req, ts) &&
!isIdentifierUsed(req, identifier)) {
cacheIdentifier(req, identifier, () => {
next();
});
} else {
if (isIdentifierUsed(req, identifier)) {
replayAttackDetected(req, res, `identifier used: ${identifier}`);
} else {
replayAttackDetected(req, res, 'max time diff exceeded');
}
}
} else {
replayAttackDetected(req, res, 'invalid req identifier - no ts identifier');
}
} else {
replayAttackDetected(req, res, 'invalid req identifier - no seperator');
}
} else {
replayAttackDetected(req, res, 'invalide req idnetifier - decode & decrypt');
}
} else {
replayAttackDetected(req, res, 'req identifier not found');
}
};
/**
* 获取请求标识.
*/
function getReqIdentifier(req) {
let reqIdentifier = req.query['reqIdentifier'] || req.headers[Consts.REQ_IDENTIFIER] || '';
return reqIdentifier;
}
/**
* 检测到重放攻击.
*/
function replayAttackDetected(req, res, msg) {
logger.warn('Replay attack detected', req.url);
logger.warn(msg);
res.status(403);
res.write('Replay attack detected', req.url);
res.end();
}
/**
* 初始化客户端服务器时间差.
* 引入客户端服务器时间差服务器当前时间与第一个合法请求时间戳的差值
* 避免时区等问题
*/
function initClientServerTimeDiff(req, ts) {
let session = req.session;
if (session[Consts.KEY_CLIENT_SERVER_TIME_DIFF] === undefined) {
let diff = getServerTimeStamp() - ts;
logger.warn('init replay attack client server time diff', diff);
session[Consts.KEY_CLIENT_SERVER_TIME_DIFF] = diff;
}
}
/**
* 判断时间戳是否超过允许时间差.
*/
function isMaxTimeDiffExceeded(req, ts) {
let session = req.session;
let diff = session[Consts.KEY_CLIENT_SERVER_TIME_DIFF];
if (diff === undefined) {
initClientServerTimeDiff(req, ts);
return false;
}
let serverTs = getServerTimeStamp();
return Math.abs(serverTs - diff - ts) > Consts.MAX_TIME_DIFF_SEC;
}
/**
* 判断请求标识是否在MAX_IDENTIFIER_LIFE_SEC时间内使用过.
*/
function isIdentifierUsed(req, identifier) {
let identifierList = getIdentifierList(req);
let hasUsed = false;
for (let i = 0; i < identifierList.length; i++ ) {
let item = identifierList[i];
if (item.id === identifier) {
hasUsed = true;
break;
}
}
return hasUsed;
}
let identifierListCache = [];
let lastSaveSessionTime = (new Date()).getTime();
let saveSessionInterval = 500; // ms
/**
* 缓存请求标识
* 为解决频繁存session存在的保存丢失问题先在内存中缓存定期保存到session.
* 多节点部署场景下保存到session后及实现多节点的数据拉通
*/
function cacheIdentifier(req, identifier, callback) {
let serverTs = getServerTimeStamp();
identifierListCache.push({
id: identifier,
ts: serverTs
});
// logger.info('req.url', req.url);
// logger.info('identifier', identifier);
// logger.info('identifierListCache', identifierListCache);
saveIdentifierListToSession(req, callback);
}
/**
* 读取请求标识列表 - session+缓存.
*/
function getIdentifierList(req) {
return getIdentifierListFromSession(req).concat(identifierListCache);
}
/**
* 从session读取请求标识列表.
*/
function getIdentifierListFromSession(req) {
let session = req.session;
if (!session[Consts.KEY_IDENTIFIER_LIST]) {
session[Consts.KEY_IDENTIFIER_LIST] = [];
}
let identifierList = session[Consts.KEY_IDENTIFIER_LIST];
return identifierList.map((item) => {
return item;
});
}
/**
* 保存请求标识列表到session.
*/
function saveIdentifierListToSession(req, callback) {
let curTime = (new Date()).getTime();
if (curTime - lastSaveSessionTime > saveSessionInterval) {
let serverTs = getServerTimeStamp();
let identifierList = req.session[Consts.KEY_IDENTIFIER_LIST] || [];
// 过滤掉超期的请求标识
identifierList = identifierList.filter((item) => {
return serverTs - item.ts < Consts.MAX_IDENTIFIER_LIFE_SEC;
});
identifierList = identifierList.concat(identifierListCache);
req.session[Consts.KEY_IDENTIFIER_LIST] = identifierList;
req.session.save(() => {
identifierListCache = [];
lastSaveSessionTime = curTime;
// logger.info('save session', identifierList);
callback();
});
} else {
callback();
}
}
/**
* 获取当前服务器时间戳.
*/
function getServerTimeStamp() {
let ts = Math.floor((new Date()).getTime() / 1000);
return ts;
}
// 加密输入输出字符集
const STR_ENCODE = 'latin1';
// 请求标识解密私钥
const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCvLS40rcyrWWVaNDZ3hlVUNnXv/NgxQ0E58jL9a5rPEY3SmSLM
sHP78+hU6dP3hAV0sLE+XlVwuHg6nnpR8rdp5YCFUZ8gJt3tCnAuqHb7o2gdfF/4
h8YI/AacYTvmhuykJ4tcWUuaevV/tY+bvxQUOYWoMfYUE6d4nXZpJNxiTQIDAQAB
AoGAQ33iyYUU0AfXVtO7EH3/Ljz1X4tJOGGzJcI9BwqIULNwsu0xTS/G4BHvN5gH
hNumxni1MVRjEtTf7aXVdvFO3Khf5YyqQ0aALHt/5GS7enyWvMjUvr+gzNOgMHqv
D7Un8Q58162gWSs0e4l/OVlZ6IrjTzcJq9aG312WrhS6zQECQQDkHYpFZyJaNyLt
O7V4j09sHlRCo5WxBNMsO5NgUTQ+w74Wkxhxg2pr/Ovd3GLWsAaaZjSx73tpLgxa
59JPXJzhAkEAxJcDRktq50f2ldnGQSmFUtUjMzDsb7Xe4P+IJzmhtpKkbK9JLDek
wioWRBL7qirkjVdavPMpcXzzxJ1at83m7QJAevE0yWzpRcf7ifIpPt22B2ZdMUEq
9ynLikZdioaZas5iT5ZdkSgrniHFGWCi4GQkWuiEs/WHBMvtmrzgSyd14QJAQQ0P
YlVWXI2thMx5E9rfaRdp/Gh19EkkJSOpNAGYB0fi7Kvb9Ku88xrmH0SAzkA/PJ3p
Qpjb39fO+gJvoF6byQJAbg26o0oKugr7X3/1Y7HrTvXhoLJYrKY6vOnsIv81Hkgw
E/PoEmvFykFcWpqW8ew8QCLjCA7iBeXCgF6Xdmjb/g==
-----END RSA PRIVATE KEY-----`;

View File

@ -0,0 +1,59 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('serialNumber');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../constants');
const request = require('request');
router.post('/', function(req, res) {
let traceId = req.headers['x-openstack-request-id'] || ''; // 获取追踪id
let session = req.session;
let body = req.body;
let baseUrl = req.baseUrl;
let restUrl = '/v3/inspur/serialnumber';
if (!baseUrl.startsWith('/')) {
baseUrl = '/' + baseUrl;
}
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let date = new Date();
let options = {
url: url+restUrl,
method: "post",
json: true,
body: body,
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
"request-time":date.getTime()+date.getTimezoneOffset()*60*1000,
"remote-ip":session[Consts.KEY_IP],
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || "",
"region-id":req.cookies.region_id||"",
'X-Openstack-Request-Id': traceId
}
};
request(options, function(error, response, body) {
if (body&&body.error) {
res.status(body.error.code);
res.json(body);
} else {
//添加成功后,需要踢出所有用户
req.sessionStore.all(function(error, obj){
for(var key in obj) {
req.sessionStore.destroy(key,function(){})
}
})
res.json(body||{});
}
});
});
return router;
};

38
server/routes/skin.js Normal file
View File

@ -0,0 +1,38 @@
module.exports = function(options, context) {
const path = require('path');
const logger = require('log4js').getLogger('skin');
const urlApi = require('url');
const router = require('express').Router();
//constants
const Consts = require('../../constants');
///^(\/login\/|\/index\/)*/
router.get(/^\/app(\.*)?.css*$/, function(req, res) {
let theme = req.cookies.theme;
let filePath = "";
let __DEV__ = process.env.NODE_ENV === 'development';
if (__DEV__) {
filePath = path.join(Consts.DEV, Consts.STATIC, 'css');
} else {
filePath = path.join(Consts.DIST, Consts.STATIC, 'css');
}
res.type('text/css; charset=utf-8');
switch (theme) {
case "light":
res.sendFile(path.join(filePath, 'app.css'));
break;
case "blue":
// res.redirect(req.baseUrl + '/app_blue.css');
res.sendFile(path.join(filePath, 'app-blue.css'));
break;
case "dark":
res.sendFile(path.join(filePath, 'app-dark.css'));
// res.redirect(req.baseUrl + '/app_dark.css');
break;
default:
res.sendFile(path.join(filePath, 'app.css'));
}
});
return router;
};

19
server/routes/static.js Normal file
View File

@ -0,0 +1,19 @@
const constants = require('../../constants'),
path = require('path');
module.exports = function( options ){
var express = require('express');
var router = express.Router();
// app.use(staticPath, express.static('./static'));
let __DEV__ = process.env.NODE_ENV === 'development';
if (__DEV__) {
router.use(constants.STATIC, express.static(path.join(constants.DEV, constants.STATIC)));
} else {
router.use(constants.STATIC, express.static(path.join(constants.DIST, constants.STATIC)));
}
// router.use('/',express.static(constants.WEB_ROOT_DIR));
router.use('/', express.static(path.join(constants.ROOT_DIR, constants.PUBLIC)));
return router;
}

View File

@ -0,0 +1,34 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('storageApi');
//Consts
const Consts = require('../constants');
var proxy = context.getResource('proxy');
var router = require('express').Router();
router.use("/:service", function(req, res) {
let session = req.session;
let body = req.body;
let moduleName = req.params.service;
req.headers[Consts.KEY_CONTENT_TYPE] = Consts.CONTENT_TYPE;
req.headers[Consts.KEY_ACCEPT] = Consts.ACCEPT;
// 会话时间
let polling = body[Consts.POLLING];
polling && (req.headers['auth-keep'] = 'false');
let language = session[Consts.KEY_LANGUAGE];
req.headers["language"] = language || "";
let timeoutControl = body[Consts.TIME_OUT_CONTROL];
res.header("Cache-Control","no-cache");
if (req.query.reqType=="https") {
proxy.web(req, res, {
target: 'https://' + moduleName,
secure:false
});
} else {
proxy.web(req, res, {
target: 'http://' + moduleName
});
}
});
return router;
};

View File

@ -0,0 +1,68 @@
module.exports = function(options, context) {
const router = require('express').Router();
const URL = require('url');
const request = require('request');
const FTPClient = require('ftp');
router.post('/', function(req, res) {
const url = req.body.url ? URL.parse(req.body.url) : URL.parse('');
if (url.protocol === 'http:' || url.protocol === 'https:') {
const options = {
method: 'GET',
url: url.href,
timeout: 5000
};
const reqForUrl = request(options)
.on('response', function(response) {
if (response.statusCode === 200) {
res.json({
errCode: 0
});
} else {
res.json({
errCode: 1
});
}
reqForUrl.abort();
})
.on('error', function(e) {
res.json({
errCode: 1
});
reqForUrl.abort();
});
} else if (url.protocol === 'ftp:') {
try {
const c = new FTPClient();
c.on('ready', function() {
c.get(url.path, function(err, stream) {
if (err) {
res.json({
errCode: 1
});
} else {
res.json({
errCode: 0
});
}
c.end();
});
});
c.connect({
host: url.hostname,
port: url.port
});
} catch (e) {
res.json({
errCode: 1
});
}
} else {
res.json({
errCode: 1
});
}
});
return router;
};

View File

@ -0,0 +1,123 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('UploadObjStorDocAction');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
const formidable = require('formidable');
const fs = require('fs');
const path = require('path');
const mime = require('mime');
// parse a file upload
router.use(function(req, res, next) {
const form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
var old_path = files.file.path,
file_size = files.file.size,
file_name = files.file.name;
// fs.readFile(old_path, function(err, data) {
// fs.unlink(old_path, function(err) {
// if (err) {
// logger.warn('Unlink ' + old_path + ' Failed');
// } else {
// logger.info('Unlink ' + old_path + ' Success');
// }
// });
if (err) {
logger.error(err);
req.files = {
error: err
};
req.fields = fields;
} else {
req.files = {
fileSize: -1, //切记,大小不一致与上次文件损坏的问题
path: old_path,
name:file_name
};
req.fields = fields;
}
next();
// });
});
});
router.post('/', function(req, res) {
let session = req.session;
let fields = req.fields;
let qParam = req.query;
let language = qParam["language"];
//let imageId = fields["imageUuid"];
let containerName = fields["containerName"];
let fileNameTemp = fields["fileName"];
let projectId = fields["projectId"];
fileNameTemp =encodeURI(fileNameTemp);
containerName = encodeURI(containerName);
projectId = encodeURI(projectId);
let restUrl = "/swift/swift/v1/" + projectId + "/" + containerName + "/" + fileNameTemp;
//let restUrl = "/glance/v2/images/" + imageId + "/file";
if (!restUrl.startsWith('/')) {
restUrl = '/' + restUrl;
}
let moduleName = restUrl.split('/')[1];
let host = session[Consts.KEY_SERVICES][moduleName];
let files = req.files;
let path = files.path;
//let opUrl = "/v2/images/" + imageId + "/file";
let opUrl = "/swift/v1/" + projectId + "/" + containerName + "/" + fileNameTemp;
let options = {
url: urlApi.resolve(host, opUrl),
//if you expect binary data, you should set encoding: null
// encoding: null, //让body 直接是buffer
method: Consts.PUT,
// json: true,
// body: {},
headers: {
'Content-type': mime.lookup(files.name), //'text/html; charset=utf-8',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
if (files['error']) {
logger.error(files['error']);
return res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: 'reqTimeout',
[Consts.KEY_RESDATA]: ''
});
}
var rs = fs.createReadStream(path);
// options.body = files.binaryData;
var httpreq = request(options, function(error, response, body) {
if (!error && (response.statusCode == 201 || response.statusCode == 204)) {
res.json({
[Consts.KEY_FLAG]: true
});
} else {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: response.statusCode,
[Consts.KEY_RESDATA]: body
});
}
});
rs.pipe(httpreq);
rs.on("error", function (err) {
logger.error('出错了:' + err);
});
rs.on("end", function() {
fs.unlink(path, function(err) {
if (err) {
logger.warn('Unlink ' + path + ' Failed');
} else {
logger.info('Unlink ' + path + ' Success');
}
});
});
});
return router;
};

View File

@ -0,0 +1,147 @@
module.exports = function (options, context) {
const exec = require('child_process').exec;
const os = require('os');
const path = require('path');
const logger = require('log4js').getLogger('UploadTemplateAction');
const urlApi = require('url');
const router = require('express').Router();
//Consts
const Consts = require('../../constants');
const request = require('request');
const formidable = require('formidable');
const fs = require('fs');
// parse a file upload
router.use(function (req, res, next) {
const form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
if (err) {
logger.error(err);
req.files = {
error: err
};
req.fields = fields;
} else {
var oldPath = files.file.path;
req.files = {
fileSize: -1, //切记,大小不一致与上次文件损坏的问题
path: oldPath
};
req.fields = fields;
}
next();
});
});
router.use(function (req, res, next) {
let session = req.session;
let platform = os.platform();
let cmd = '';
if (platform === 'linux') {
cmd = 'exec qemu-img';
} else if (platform === 'win32') {
cmd = path.resolve(__dirname, '..\\..\\lib\\qemu-img\\win32\\qemu-img.exe');
}
let fileName = req.files.path;
cmd = `${cmd} info ${fileName}`;
exec(cmd, function (error, stdout, stderr) {
if (error) {
logger.error(error);
next();
} else {
console.log(stdout);
let reg = /virtual size: .* \((\d*) bytes\)/;
let matches = stdout.match(reg);
if (matches === null) {
logger.error('cmd get qemu-img info cannot match virtual size.');
next();
} else {
let id = req.fields.imageUuid;
let host = session[Consts.KEY_SERVICES]['glance'];
let restUrl = `/v2/images/${id}`;
let options = {
url: urlApi.resolve(host, restUrl),
method: 'patch',
body: JSON.stringify([{
"op": "add",
"path": "/inspur_virtual_size",
"value": `${matches[1]}`
}]),
headers: {
'Content-Type': 'application/openstack-images-v2.1-json-patch',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
console.log(options);
request(options, (error, response, body) => {
console.log('error:', error);
console.log('body:', body);
next();
});
}
}
});
});
router.post('/', function (req, res) {
let session = req.session;
let fields = req.fields;
let imageId = fields["imageUuid"];
let restUrl = "/glance/v2/images/" + imageId + "/file";
if (!restUrl.startsWith('/')) {
restUrl = '/' + restUrl;
}
let moduleName = restUrl.split('/')[1];
let host = session[Consts.KEY_SERVICES][moduleName];
let opUrl = "/v2/images/" + imageId + "/file";
let options = {
url: urlApi.resolve(host, opUrl),
method: Consts.PUT,
headers: {
'Content-type': 'application/octet-stream',
'auth-token': session[Consts.KEY_TOKEN],
'X-Auth-Token': session[Consts.PROJECT_TOKEN],
'language': session[Consts.KEY_LANGUAGE]
}
};
let files = req.files;
if (files['error']) {
logger.error(files['error']);
return res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: files['name'] + '上传失败,文件上传超时或网络异常,请重新上传!',
[Consts.KEY_RESDATA]: ''
});
}
let fileName = files.path;
var rs = fs.createReadStream(fileName);
var httpreq = request(options, function (error, response, body) {
if (!error && (response.statusCode === 200 || response.statusCode === 204)) {
res.json({
[Consts.KEY_FLAG]: true
});
} else {
res.json({
[Consts.KEY_FLAG]: false,
[Consts.KEY_ERRCODE]: files['name'] + '上传失败!',
[Consts.KEY_RESDATA]: ''
});
}
});
rs.pipe(httpreq);
rs.on("end", function () {
fs.unlink(fileName, function (err) {
if (err) {
logger.warn('Unlink ' + fileName + ' Failed');
} else {
logger.info('Unlink ' + fileName + ' Success');
}
});
});
});
return router;
};

View File

@ -0,0 +1,24 @@
module.exports = function(options, context) {
const logger = require('log4js').getLogger('websocket');
const Consts = require('../constants');
var proxy = context.getResource('proxy');
var serviceAddr = context.getResource('serviceAddr.json');
var serviceIP = serviceAddr['keystone'].split(":")[0];
var router = require('express').Router();
try {
router.use('/:service', function(req, res, head) {
let session = req.session;
req.headers['origin'] = 'wss://'+serviceIP+':6678';
req.headers["Authorization"] = decodeURIComponent(req.query.key);
proxy.ws(req, res.socket, head, {
target: 'wss://'+serviceIP+':6678',
ws: true,
secure: false,
changeOrigin: true
});
});
} catch (error) {
logger.error('error proxy websocket', error.stack);
}
return router;
};

BIN
skyline-logo-90x90.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
skyline-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
src/assets/img/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
src/assets/img/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/img/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Some files were not shown because too many files have changed in this diff Show More