shadowsocks-manager
Version:
A shadowsocks manager tool for multi user and traffic control.
886 lines (860 loc) • 28.3 kB
JavaScript
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 groupPlugin = appRequire('plugins/group/index');
const push = appRequire('plugins/webgui/server/push');
const macAccount = appRequire('plugins/macAccount/index');
const ref = appRequire('plugins/webgui_ref/index');
const axios = require('axios');
const TwitterLogin = appRequire('plugins/webgui/server/twitterLogin');
const redis = appRequire('init/redis').redis;
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();
};
const getRandomPassword = passowrdLength => {
const passwordChars = '23456789ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
const password = Array(passowrdLength).fill(passwordChars).map(function(x) { return x[Math.floor(Math.random() * x.length)]; }).join('');
return password;
};
const getNewPort = async () => {
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', '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');
}
});
}
});
};
const createUser = async (email, password, from = '') => {
let type = 'normal';
await knex('user').count('id AS count').then(success => {
if(!success[0].count) {
type = 'admin';
}
});
let group = 0;
const webguiSetting = await knex('webguiSetting').select().where({
key: 'account',
}).then(success => JSON.parse(success[0].value));
if(webguiSetting.defaultGroup) {
try {
await groupPlugin.getOneGroup(webguiSetting.defaultGroup);
group = webguiSetting.defaultGroup;
} catch(err) {}
}
const [ userId ] = await user.add({
username: email,
email,
password,
type,
group,
});
if(userId === 1) {
return {
id: userId,
type: 'admin',
};
}
const newUserAccount = webguiSetting.accountForNewUser;
if(newUserAccount.isEnable) {
const port = await getNewPort();
if(newUserAccount.fromOrder) {
const orderInfo = await knex('webgui_order').where({ id: newUserAccount.type }).then(s => s[0]);
if(orderInfo) {
await account.addAccount(orderInfo.type || 5, {
user: userId,
orderId: orderInfo.id,
port,
password: getRandomPassword(10),
time: Date.now(),
limit: orderInfo.cycle,
flow: orderInfo.flow,
server: orderInfo.server,
autoRemove: orderInfo.autoRemove ? 1 : 0,
multiServerFlow: orderInfo.multiServerFlow ? 1 : 0,
});
}
} else {
await account.addAccount(newUserAccount.type || 5, {
user: userId,
orderId: 0,
port,
password: getRandomPassword(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,
});
}
}
logger.info(`[${ email }] signup success`);
push.pushMessage('注册', {
body: `${ from }用户[ ${ email.toString().toLowerCase() } ]注册成功`,
});
isTelegram && telegram.push(`${ from }用户[ ${ email.toString().toLowerCase() } ]注册成功`);
return {
id: userId,
type: 'normal',
};
};
exports.signup = async (req, res) => {
try {
req.checkBody('email', 'Invalid email').isEmail();
req.checkBody('code', 'Invalid code').notEmpty();
req.checkBody('password', 'Invalid password').notEmpty();
let type = 'normal';
const validation = await req.getValidationResult();
if(!validation.isEmpty()) { throw(validation.array()); }
const email = req.body.email.toString().toLowerCase();
const code = req.body.code;
await emailPlugin.checkCode(email, code);
await knex('user').count('id AS count').then(success => {
if(!success[0].count) {
type = 'admin';
}
});
const password = req.body.password;
let group = 0;
const webguiSetting = await knex('webguiSetting').select().where({
key: 'account',
}).then(success => JSON.parse(success[0].value));
if(webguiSetting.defaultGroup) {
try {
await groupPlugin.getOneGroup(webguiSetting.defaultGroup);
group = webguiSetting.defaultGroup;
} catch(err) {}
}
const [ userId ] = await user.add({
username: email,
email,
password,
type,
group,
});
req.session.user = userId;
req.session.type = type;
if(req.body.ref) { ref.addRefUser(req.body.ref, req.session.user); }
if(userId === 1) {
res.send(type);
return;
}
const newUserAccount = webguiSetting.accountForNewUser;
if(newUserAccount.isEnable) {
const port = await getNewPort();
if(newUserAccount.fromOrder) {
const orderInfo = await knex('webgui_order').where({ id: newUserAccount.type }).then(s => s[0]);
if(orderInfo) {
await account.addAccount(orderInfo.type || 5, {
user: userId,
orderId: orderInfo.id,
port,
password: getRandomPassword(10),
time: Date.now(),
limit: orderInfo.cycle,
flow: orderInfo.flow,
server: orderInfo.server,
autoRemove: orderInfo.autoRemove ? 1 : 0,
multiServerFlow: orderInfo.multiServerFlow ? 1 : 0,
});
}
} else {
await account.addAccount(newUserAccount.type || 5, {
user: userId,
orderId: 0,
port,
password: getRandomPassword(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,
});
}
}
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 }`);
const errorData = ['user exists'];
if(errorData.indexOf(err) < 0) {
return res.status(403).end();
} else {
return res.status(403).end(err);
}
}
};
exports.login = async (req, res) => {
try {
delete req.session.user;
delete req.session.type;
req.checkBody('email', 'Invalid email').isEmail();
req.checkBody('password', 'Invalid password').notEmpty();
const validation = await req.getValidationResult();
if(!validation.isEmpty()) {
throw('invalid body');
}
const email = req.body.email.toString().toLowerCase();
const password = req.body.password;
const result = await user.checkPassword(email, password);
logger.info(`[${ req.body.email }] login success`);
req.session.user = result.id;
req.session.type = result.type;
res.send({
type: result.type,
id: result.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.googleLogin = async (req, res) => {
try {
const { code, redirect_uri } = req.body;
const {
google_login_client_id: client_id,
google_login_client_secret: client_secret
} = config.plugins.webgui;
if(!code || !client_id) {
return await Promise.reject();
}
const { data: result } = await axios({
url: 'https://www.googleapis.com/oauth2/v4/token',
method: 'POST',
data: {
code,
client_id,
client_secret,
redirect_uri,
grant_type: 'authorization_code',
},
});
if(!result.access_token) { return await Promise.reject(); }
const { data: userInfo } = await axios({
url: 'https://www.googleapis.com/oauth2/v1/userinfo',
method: 'GET',
params: {
alt: 'json',
},
headers: {
Authorization: `Bearer ${ result.access_token }`,
},
});
if(userInfo.verified_email && userInfo.email) {
const email = userInfo.email;
const user = await knex('user').where({ username: email }).then(s => s[0]);
if(user) {
req.session.user = user.id;
req.session.type = user.type;
logger.info(`Google用户[${email}]登录`);
return res.send({ id: user.id, type: user.type });
} else {
const password = Math.random().toString();
const user = await createUser(email, password, 'Google');
req.session.user = user.id;
req.session.type = user.type;
return res.send({ id: user.id, type: user.type });
}
}
return await Promise.reject();
} catch(err) {
logger.error(err);
return res.status(403).end();
}
};
let facebookAppToken = '';
const getFacebookAppToken = async () => {
if(facebookAppToken) { return facebookAppToken; }
const {
facebook_login_client_id: client_id,
facebook_login_client_secret: client_secret,
} = config.plugins.webgui;
const { data: result } = await axios({
url: 'https://graph.facebook.com/oauth/access_token',
headers: {
Accept: 'application/json',
},
params: {
client_id,
client_secret,
grant_type: 'client_credentials',
},
});
if(!result.access_token) { return Promise.reject(); }
facebookAppToken = result.access_token;
return result.access_token;
};
exports.facebookLogin = async (req, res) => {
try {
const { code, redirect_uri } = req.body;
const {
facebook_login_client_id: client_id,
facebook_login_client_secret: client_secret,
} = config.plugins.webgui;
if(!code || !client_id) {
return Promise.reject();
}
const { data: result } = await axios({
url: 'https://graph.facebook.com/v3.3/oauth/access_token',
method: 'GET',
headers: {
Accept: 'application/json',
},
params: {
code,
client_id,
client_secret,
redirect_uri,
},
});
if(!result.access_token) { return Promise.reject(); }
const { data: checkToken } = await axios({
url: 'https://graph.facebook.com/debug_token',
method: 'GET',
headers: {
Accept: 'application/json',
},
params: {
input_token: result.access_token,
access_token: await getFacebookAppToken(),
},
});
if(!checkToken.data || checkToken.data.app_id !== client_id || !checkToken.data.is_valid) {
return Promise.reject();
}
const { data: userInfo } = await axios({
url: 'https://graph.facebook.com/me',
method: 'POST',
headers: {
Accept: 'application/json',
},
params: {
fields: 'email',
access_token: result.access_token,
},
});
if(userInfo.email) {
const email = userInfo.email;
const user = await knex('user').where({ username: email }).then(s => s[0]);
if(user) {
req.session.user = user.id;
req.session.type = user.type;
logger.info(`Facebook用户[${email}]登录`);
return res.send({ id: user.id, type: user.type });
} else {
const password = Math.random().toString();
const user = await createUser(email, password, 'Facebook');
req.session.user = user.id;
req.session.type = user.type;
return res.send({ id: user.id, type: user.type });
}
}
return Promise.reject();
} catch(err) {
logger.error(err);
return res.status(403).end();
}
};
exports.githubLogin = async (req, res) => {
try {
const { code, redirect_uri, state } = req.body;
const {
github_login_client_id: client_id,
github_login_client_secret: client_secret,
} = config.plugins.webgui;
if(!code || !client_id) {
return await Promise.reject();
}
const { data: result } = await axios({
url: 'https://github.com/login/oauth/access_token',
method: 'POST',
headers: {
Accept: 'application/json',
},
data: {
code,
client_id,
client_secret,
redirect_uri,
state,
},
});
if(!result.access_token) { return await Promise.reject(); }
const { data: userInfo } = await axios({
url: 'https://api.github.com/user',
method: 'GET',
headers: {
'User-Agent': 'ssmgr',
Authorization: `token ${ result.access_token }`,
},
});
const { data: emails } = await axios({
url: 'https://api.github.com/user/emails',
method: 'GET',
headers: {
'User-Agent': 'ssmgr',
Authorization: `token ${ result.access_token }`,
},
});
const emailInfo = emails.filter(f => f.primary === true)[0];
if(emailInfo.email && emailInfo.verified) {
const email = emailInfo.email;
const user = await knex('user').where({ username: email }).then(s => s[0]);
if(user) {
req.session.user = user.id;
req.session.type = user.type;
logger.info(`Github用户[${email}]登录`);
return res.send({ id: user.id, type: user.type });
} else {
const password = Math.random().toString();
const user = await createUser(email, password, 'Github');
req.session.user = user.id;
req.session.type = user.type;
return res.send({ id: user.id, type: user.type });
}
}
return await Promise.reject();
} catch(err) {
logger.error(err);
return res.status(403).end();
}
};
exports.getTwitterLoginUrl = async (req, res) => {
try {
const { callbackUrl } = req.query;
const time = callbackUrl.split('?time=')[1];
const {
twitter_login_consumer_key: consumerKey,
twitter_login_consumer_secret: consumerSecret
} = config.plugins.webgui;
if(!callbackUrl || !time || !consumerKey || !consumerSecret) {
throw('invalid params');
}
if(Math.abs(Date.now() - (+time)) >= 10 * 60 * 1000) {
throw('invalid time');
}
const tl = new TwitterLogin({
consumerKey,
consumerSecret,
callbackUrl,
});
const { tokenSecret, url } = await tl.login();
await redis.set(`TwitterLogin:${time}`, tokenSecret, 'EX', 120);
res.send(url);
} catch(err) {
logger.error(err);
return res.status(403).end();
}
};
exports.twitterLogin = async (req, res) => {
try {
const { oauth_token, oauth_verifier, callbackUrl } = req.body;
const time = callbackUrl.split('?time=')[1];
if(Math.abs(Date.now() - (+time)) >= 10 * 60 * 1000) {
throw('invalid time');
}
const {
twitter_login_consumer_key: consumerKey,
twitter_login_consumer_secret: consumerSecret
} = config.plugins.webgui;
const tl = new TwitterLogin({
consumerKey,
consumerSecret,
callbackUrl,
});
const tokenSecret = await redis.get(`TwitterLogin:${time}`);
const { userToken, userTokenSecret } = await tl.callback({ oauth_token, oauth_verifier }, tokenSecret);
const userInfo = await tl.userInfo({ userToken, userTokenSecret });
const email = userInfo.email;
const user = await knex('user').where({ username: email }).then(s => s[0]);
if(user) {
req.session.user = user.id;
req.session.type = user.type;
logger.info(`Twitter用户[${email}]登录`);
return res.send({ id: user.id, type: user.type });
} else {
const password = Math.random().toString();
const user = await createUser(email, password, 'Twitter');
req.session.user = user.id;
req.session.type = user.type;
return res.send({ id: user.id, type: user.type });
}
} catch(err) {
logger.error(err);
return res.status(403).end();
}
};
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 => JSON.parse(success[0].value));
const account = await knex('webguiSetting').select().where({
key: 'account',
}).then(success => JSON.parse(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';
const language = config.plugins.webgui.language || '';
const google_login_client_id = config.plugins.webgui.google_login_client_id || '';
const facebook_login_client_id = config.plugins.webgui.facebook_login_client_id || '';
const github_login_client_id = config.plugins.webgui.github_login_client_id || '';
const twitter_login_client_id = !!config.plugins.webgui.twitter_login_consumer_key;
const crisp = (config.plugins.webgui_crisp && config.plugins.webgui_crisp.use) ? config.plugins.webgui_crisp.websiteId : '';
let alipay;
let paypal;
let paypalMode;
let telegram;
let giftcard;
let refCode;
let email;
let subscribe;
let multiAccount;
let simple;
let macAccount;
let showAllServer;
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;
simple = (await knex('webguiSetting').select().where({
key: 'account',
}).then(success => {
success[0].value = JSON.parse(success[0].value);
return success[0].value;
})).simple;
macAccount = (await knex('webguiSetting').select().where({
key: 'account',
}).then(success => {
success[0].value = JSON.parse(success[0].value);
return success[0].value;
})).macAccount;
}
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;
showAllServer = account.showAllServer;
}
res.send({
status,
id,
email,
version,
themePrimary,
themeAccent,
browserColor,
site,
skin,
language,
alipay,
paypal,
paypalMode,
telegram,
giftcard,
refCode,
subscribe,
multiAccount,
simple,
macAccount,
google_login_client_id,
facebook_login_client_id,
github_login_client_id,
twitter_login_client_id,
crisp,
showAllServer,
});
} catch(err) {
logger.error(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();
});
};