UNPKG

shadowsocks-manager

Version:

A shadowsocks manager tool for multi user and traffic control.

480 lines (465 loc) 16.2 kB
const log4js = require('log4js'); const logger = log4js.getLogger('webgui'); const config = appRequire('services/config').all(); const user = appRequire('plugins/user/index'); const account = appRequire('plugins/account/index'); const flow = appRequire('plugins/flowSaver/flow'); const knex = appRequire('init/knex').knex; const emailPlugin = appRequire('plugins/email/index'); const push = appRequire('plugins/webgui/server/push'); const macAccount = appRequire('plugins/macAccount/index'); const ref = appRequire('plugins/webgui_ref/index'); const isTelegram = config.plugins.webgui_telegram && config.plugins.webgui_telegram.use; let telegram; if(isTelegram) { telegram = appRequire('plugins/webgui_telegram/admin'); } const formatMacAddress = mac => { return mac.replace(/-/g, '').replace(/:/g, '').toLowerCase(); }; exports.signup = (req, res) => { req.checkBody('email', 'Invalid email').isEmail(); req.checkBody('code', 'Invalid code').notEmpty(); req.checkBody('password', 'Invalid password').notEmpty(); let type = 'normal'; req.getValidationResult().then(result => { if(result.isEmpty()) { const email = req.body.email.toString().toLowerCase(); const code = req.body.code; return emailPlugin.checkCode(email, code); } return Promise.reject('invalid body'); }).then(success => { // The first user will be admin return knex('user').count('id AS count').then(success => { if(!success[0].count) { type = 'admin'; } return; }); }).then(success => { const email = req.body.email.toString().toLowerCase(); const password = req.body.password; return user.add({ username: email, email, password, type, }); }).then(success => { req.session.user = success[0]; req.session.type = type; if(req.body.ref) { ref.addRefUser(req.body.ref, req.session.user); } if(success[0] > 1) { const userId = success[0]; // let port = 50000; return knex('webguiSetting').select().where({ key: 'account', }) .then(success => JSON.parse(success[0].value)) .then(success => { const newUserAccount = success.accountForNewUser; if(!success.accountForNewUser.isEnable) { return; } const getNewPort = () => { return knex('webguiSetting').select().where({ key: 'account', }).then(success => { if(!success.length) { return Promise.reject('settings not found'); } success[0].value = JSON.parse(success[0].value); return success[0].value.port; }).then(port => { if(port.random) { const getRandomPort = () => Math.floor(Math.random() * (port.end - port.start + 1) + port.start); let retry = 0; let myPort = getRandomPort(); const checkIfPortExists = port => { let myPort = port; return knex('account_plugin').select() .where({ port }).then(success => { if(success.length && retry <= 30) { retry++; myPort = getRandomPort(); return checkIfPortExists(myPort); } else if (success.length && retry > 30) { return Promise.reject('Can not get a random port'); } else { return myPort; } }); }; return checkIfPortExists(myPort); } else { // return knex('account_plugin').select() // .whereBetween('port', [port.start, port.end]) // .orderBy('port', 'DESC').limit(1).then(success => { // if(success.length) { // return success[0].port + 1; // } // return port.start; // }); return knex('account_plugin').select() .whereBetween('port', [port.start, port.end]) .orderBy('port', 'ASC').then(success => { const portArray = success.map(m => m.port); let myPort; for(let p = port.start; p <= port.end; p++) { if(portArray.indexOf(p) < 0) { myPort = p; break; } } if(myPort) { return myPort; } else { return Promise.reject('no port'); } }); } }); }; getNewPort().then(port => { return account.addAccount(newUserAccount.type || 5, { user: userId, orderId: 0, port, password: Math.random().toString().substr(2,10), time: Date.now(), limit: newUserAccount.limit || 8, flow: (newUserAccount.flow ? newUserAccount.flow : 350) * 1000000, server: newUserAccount.server ? JSON.stringify(newUserAccount.server): null, autoRemove: newUserAccount.autoRemove ? 1 : 0, multiServerFlow: newUserAccount.multiServerFlow ? 1 : 0, }); }); }); } else { return; } }).then(success => { logger.info(`[${ req.body.email }] signup success`); push.pushMessage('注册', { body: `用户[ ${ req.body.email.toString().toLowerCase() } ]注册成功`, }); isTelegram && telegram.push(`用户[ ${ req.body.email.toString().toLowerCase() } ]注册成功`); res.send(type); }).catch(err => { logger.error(`[${ req.body.email }] signup fail: ${ err }`); // res.status(403).end(); const errorData = ['user exists']; if(errorData.indexOf(err) < 0) { return res.status(403).end(); } else { return res.status(403).end(err); } }); }; exports.login = (req, res) => { delete req.session.user; delete req.session.type; req.checkBody('email', 'Invalid email').isEmail(); req.checkBody('password', 'Invalid password').notEmpty(); req.getValidationResult().then(result => { if(result.isEmpty()) { const email = req.body.email.toString().toLowerCase(); const password = req.body.password; return user.checkPassword(email, password); } return Promise.reject('invalid body'); }).then(success => { logger.info(`[${ req.body.email }] login success`); req.session.user = success.id; req.session.type = success.type; res.send({ type: success.type, id: success.id, }); }).catch(err => { logger.error(`User[${ req.body.email }] login fail: ${ err }`); const errorData = ['invalid body', 'user not exists', 'invalid password', 'password retry out of limit']; if(errorData.indexOf(err) < 0) { return res.status(500).end(); } else { return res.status(403).end(err); } }); }; exports.macLogin = (req, res) => { delete req.session.user; delete req.session.type; const mac = formatMacAddress(req.body.mac); const ip = req.headers['x-real-ip'] || req.connection.remoteAddress; macAccount.login(mac, ip) .then(success => { req.session.user = success.userId; req.session.type = 'normal'; return res.send('success'); }).catch(err => { return res.status(403).end(); }); }; exports.logout = (req, res) => { delete req.session.user; delete req.session.type; res.send('success'); }; exports.status = async (req, res) => { const colors = [ { value: 'red', color: '#F44336' }, { value: 'pink', color: '#E91E63' }, { value: 'purple', color: '#9C27B0' }, { value: 'deep-purple', color: '#673AB7' }, { value: 'indigo', color: '#3F51B5' }, { value: 'blue', color: '#2196F3' }, { value: 'light-blue', color: '#03A9F4' }, { value: 'cyan', color: '#00BCD4' }, { value: 'teal', color: '#009688' }, { value: 'green', color: '#4CAF50' }, { value: 'light-green', color: '#8BC34A' }, { value: 'lime', color: '#CDDC39' }, { value: 'yellow', color: '#FFEB3B' }, { value: 'amber', color: '#FFC107' }, { value: 'orange', color: '#FF9800' }, { value: 'deep-orange', color: '#FF5722' }, { value: 'brown', color: '#795548' }, { value: 'blue-grey', color: '#607D8B' }, { value: 'grey', color: '#9E9E9E' }, ]; try { const base = (await knex('webguiSetting').select().where({ key: 'base', }).then(success => { success[0].value = JSON.parse(success[0].value); return success[0].value; })); const themePrimary = base.themePrimary; const themeAccent = base.themeAccent; const filterColor = colors.filter(f => f.value === base.themePrimary); const browserColor = filterColor[0] ? filterColor[0].color : '#3F51B5'; const status = req.session.type; // admin/normal/undefined const id = req.session.user; const version = appRequire('package').version; const site = config.plugins.webgui.site; const skin = config.plugins.webgui.skin || 'default'; let alipay; let paypal; let paypalMode; let telegram; let giftcard; let refCode; let email; let subscribe; let multiAccount; if(status) { email = (await knex('user').select(['email']).where({ id }).then(s => s[0])).email; alipay = config.plugins.alipay && config.plugins.alipay.use; paypal = config.plugins.paypal && config.plugins.paypal.use; paypalMode = config.plugins.paypal && config.plugins.paypal.mode; telegram = config.plugins.webgui_telegram && config.plugins.webgui_telegram.use; giftcard = config.plugins.giftcard && config.plugins.giftcard.use; refCode = (await knex('webguiSetting').select().where({ key: 'webgui_ref', }).then(success => { success[0].value = JSON.parse(success[0].value); return success[0].value; })).useRef; subscribe = (await knex('webguiSetting').select().where({ key: 'account', }).then(success => { success[0].value = JSON.parse(success[0].value); return success[0].value; })).subscribe; } if(status === 'normal') { knex('user').update({ lastLogin: Date.now() }).where({ id }).then(); const groupId = (await knex('user').select(['group']).where({ id }).then(s => s[0])).group; multiAccount = (await knex('group').where({ id: groupId }).then(s => s[0])).multiAccount; } res.send({ status, id, email, version, themePrimary, themeAccent, browserColor, site, skin, alipay, paypal, paypalMode, telegram, giftcard, refCode, subscribe, multiAccount, }); } catch(err) { console.log(err); delete req.session.user; delete req.session.type; return res.status(403).end(); } }; exports.sendCode = (req, res) => { const refCode = req.body.refCode; req.checkBody('email', 'Invalid email').isEmail(); req.getValidationResult().then(result => { if(result.isEmpty) { return; } return Promise.reject('invalid email'); }).then(() => { return knex('webguiSetting').select().where({ key: 'account', }) .then(success => JSON.parse(success[0].value)) .then(success => { if(success.signUp.isEnable) { return; } if(refCode) { return ref.checkRefCodeForSignup(refCode).then(success => { if(success) { return; } return Promise.reject('invalid ref code'); }); } return Promise.reject('signup close'); }); }).then(() => { return knex('webguiSetting').select().where({ key: 'mail', }).then(success => { if(!success.length) { return Promise.reject('settings not found'); } success[0].value = JSON.parse(success[0].value); return success[0].value.code; }); }).then(success =>{ const email = req.body.email.toString().toLowerCase(); const ip = req.headers['x-real-ip'] || req.connection.remoteAddress; const session = req.sessionID; return emailPlugin.sendCode(email, success.title || 'ss验证码', success.content || '欢迎新用户注册,\n您的验证码是:', { ip, session, }); }).then(success => { res.send('success'); }).catch(err => { logger.error(err); const errorData = ['email in black list', 'send email out of limit', 'signup close', 'invalid ref code']; if(errorData.indexOf(err) < 0) { return res.status(403).end(); } else { return res.status(403).end(err); } }); }; exports.sendResetPasswordEmail = (req, res) => { const crypto = require('crypto'); const email = req.body.email.toString().toLowerCase(); let token = null; let resetEmail; knex('webguiSetting').select().where({ key: 'mail', }).then(success => { if(!success.length) { return Promise.reject('settings not found'); } success[0].value = JSON.parse(success[0].value); return success[0].value.reset; }).then(success => { resetEmail = success; return knex('user').select().where({ username: email, }).then(users => { if(!users.length) { return Promise.reject('user not exists'); } return users[0]; }); }).then(user => { if(user.resetPasswordTime + 600 * 1000 >= Date.now()) { return Promise.reject('already send'); } token = crypto.randomBytes(16).toString('hex'); const ip = req.headers['x-real-ip'] || req.connection.remoteAddress; const session = req.sessionID; const address = config.plugins.webgui.site + '/home/password/reset/' + token; if(resetEmail.content.indexOf('${address}') >= 0) { resetEmail.content = resetEmail.content.replace(/\$\{address\}/g, address); } else { resetEmail.content += '\n' + address; } return emailPlugin.sendMail(email, resetEmail.title, resetEmail.content, { ip, session, type: 'reset', }); }).then(success => { return user.edit({ username: email, }, { resetPasswordId: token, resetPasswordTime: Date.now(), }); }).then(success => { res.send('success'); }).catch(err => { logger.error(err); const errorData = ['already send', 'user not exists']; if(errorData.indexOf(err) < 0) { return res.status(403).end(); } else { return res.status(403).end(err); } }); }; exports.checkResetPasswordToken = (req, res) => { const token = req.query.token; knex('user').select().where({ resetPasswordId: token, }).whereBetween('resetPasswordTime', [ Date.now() - 600 * 1000, Date.now() ]) .then(users => { if(!users.length) { return Promise.reject('user not exists'); } return users[0]; }).then(success => { res.send('success'); }).catch(err => { console.log(err); res.status(403).end(); }); }; exports.resetPassword = (req, res) => { req.checkBody('token', 'Invalid token').notEmpty(); req.checkBody('password', 'Invalid password').notEmpty(); req.getValidationResult().then(result => { if(result.isEmpty) { return; } return Promise.reject('invalid body'); }).then(() => { const token = req.body.token; const password = req.body.password; return user.edit({ resetPasswordId: token, }, { password, resetPasswordId: null, resetPasswordTime: null, }); }).then(success => { res.send('success'); }).catch(err => { logger.error(err); res.status(403).end(); }); }; exports.visitRef = (req, res) => { const code = req.params.refCode; ref.visitRefCode(code).then(success => { res.send({ valid: success }); }).catch(err => { logger.error(err); res.status(403).end(); }); };