shadowsocks-manager
Version:
A shadowsocks manager tool for multi user and traffic control.
347 lines (328 loc) • 11.1 kB
JavaScript
const knex = appRequire('init/knex').knex;
const serverPlugin = appRequire('plugins/flowSaver/server');
const accountPlugin = appRequire('plugins/account/index');
const flow = appRequire('plugins/flowSaver/flow');
const dns = require('dns');
const net = require('net');
const config = appRequire('services/config').all();
const getFlow = async (serverId, accountId) => {
const where = { accountId };
if(serverId) { where.serverId = serverId; }
const result = await knex('account_flow').sum('flow as sumFlow').groupBy('accountId').where(where).then(s => s[0]);
return result ? result.sumFlow : -1;
};
const formatMacAddress = mac => mac.replace(/-/g, '').replace(/:/g, '').toLowerCase();
const loginLog = {};
const scanLoginLog = ip => {
for(let i in loginLog) {
if(Date.now() - loginLog[i].time >= 10 * 60 * 1000) {
delete loginLog[i];
}
}
if(!loginLog[ip]) {
return false;
} else if (loginLog[ip].mac.length <= 10) {
return false;
} else {
return true;
}
};
const loginFail = (mac, ip) => {
if(!loginLog[ip]) {
loginLog[ip] = { mac: [ mac ], time: Date.now() };
} else {
if(loginLog[ip].mac.indexOf(mac) < 0) {
loginLog[ip].mac.push(mac);
loginLog[ip].time = Date.now();
}
}
};
const getIp = address => {
let myAddress = address;
if(address.indexOf(':') >= 0) {
const hosts = address.split(':');
const number = Math.ceil(Math.random() * (hosts.length - 1));
myAddress = hosts[number];
}
if(net.isIP(myAddress)) {
return Promise.resolve(myAddress);
}
return new Promise((resolve, reject) => {
dns.lookup(myAddress, (err, myAddress, family) => {
if(err) {
return reject(err);
}
return resolve(myAddress);
});
});
};
const newAccount = (mac, userId, serverId, accountId) => {
return knex('mac_account').insert({
mac, userId, serverId, accountId,
});
};
const getAccount = async (userId, group) => {
const where = {};
where['mac_account.userId'] = userId;
if(group >= 0) {
where['user.group'] = group;
}
const macAccounts = await knex('mac_account').select([
'mac_account.id as id',
'mac_account.userId as userId',
'mac_account.mac as mac',
'mac_account.accountId as accountId',
'mac_account.serverId as serverId',
]).where(where).leftJoin('user', 'user.id', 'mac_account.userId');
const accounts = await knex('account_plugin').where({ userId });
macAccounts.forEach(macAccount => {
const isExists = accounts.filter(f => {
return f.id === macAccount.accountId;
})[0];
if(!isExists && accounts.length) {
knex('mac_account').update({
accountId: accounts[0].id
}).where({
id: macAccount.id
}).then();
}
});
return macAccounts;
};
const getNoticeForUser = async (mac, ip) => {
if(scanLoginLog(ip)) {
return Promise.reject('ip is in black list');
}
const macAccount = await knex('mac_account').where({ mac }).then(success => success[0]);
if(!macAccount) {
loginFail(mac, ip);
return Promise.reject('mac account not found');
}
const userId = macAccount.userId;
const groupInfo = await knex('user').select([
'group.id as id',
'group.showNotice as showNotice',
]).innerJoin('group', 'user.group', 'group.id').where({
'user.id': userId,
}).then(s => s[0]);
const group = [groupInfo.id];
if(groupInfo.showNotice) { group.push(-1); }
const notices = await knex('notice').select().whereIn('group', group).orderBy('time', 'desc');
return notices;
};
const getAccountForUser = async (mac, ip, opt) => {
const noPassword = opt.noPassword;
const noFlow = opt.noFlow;
if(scanLoginLog(ip)) {
return Promise.reject('ip is in black list');
}
const macAccount = await knex('mac_account').where({ mac }).then(success => success[0]);
if(!macAccount) {
loginFail(mac, ip);
return Promise.reject('mac account not found');
}
await getAccount(macAccount.userId);
const myServerId = macAccount.serverId;
const myAccountId = macAccount.accountId;
const accounts = await knex('mac_account').select([
'mac_account.id',
'mac_account.mac',
'account_plugin.id as accountId',
'account_plugin.port',
'account_plugin.password',
'account_plugin.multiServerFlow as multiServerFlow',
])
.leftJoin('user', 'mac_account.userId', 'user.id')
.leftJoin('account_plugin', 'mac_account.userId', 'account_plugin.userId');
const account = accounts.filter(a => {
return a.accountId === myAccountId;
})[0];
const accountData = (await accountPlugin.getAccount({ id: myAccountId }))[0];
accountData.data = JSON.parse(accountData.data);
let startTime = 0;
let expire = 0;
if(accountData.type >= 2 && accountData.type <= 5) {
let timePeriod = 0;
if(accountData.type === 2) { timePeriod = 7 * 86400 * 1000; }
if(accountData.type === 3) { timePeriod = 30 * 86400 * 1000; }
if(accountData.type === 4) { timePeriod = 1 * 86400 * 1000; }
if(accountData.type === 5) { timePeriod = 3600 * 1000; }
startTime = accountData.data.create;
while(startTime + timePeriod <= Date.now()) {
startTime += timePeriod;
}
expire = accountData.data.create + accountData.data.limit * timePeriod;
}
const isMultiServerFlow = account.multiServerFlow;
const servers = await serverPlugin.list({ status: false });
let server = servers.filter(s => {
return s.id === myServerId;
})[0];
if(!server) {
server = servers[0];
}
const address = await getIp(server.host);
const validServers = JSON.parse(accountData.server);
const serverList = servers.filter(f => {
if(!validServers) {
return true;
} else {
return validServers.indexOf(f.id) >= 0;
}
}).map(f => {
let serverInfo;
return getIp(f.host).then(success => {
serverInfo = {
id: f.id,
name: f.name,
address: success,
port: account.port + f.shift,
method: f.method,
comment: f.comment,
};
return serverInfo;
}).then(success => {
if(startTime && !noFlow) {
return getFlow(isMultiServerFlow ? null : success.id, account.accountId);
// return flow.getFlowFromSplitTime(isMultiServerFlow ? null : success.id, account.accountId, startTime, Date.now());
} else {
return -1;
}
}).then(success => {
serverInfo.currentFlow = success;
if(startTime) {
serverInfo.flow = accountData.data.flow * (isMultiServerFlow ? 1 : f.scale);
} else {
serverInfo.flow = -1;
}
serverInfo.expire = expire || null;
return knex('account_flow').select(['status']).where({
serverId: f.id,
accountId: account.accountId,
}).then(s => {
if(!s.length) { return 'checked'; }
return s[0].status;
});
}).then(success => {
serverInfo.status = success;
serverInfo.base64 = 'ss://' + Buffer.from(server.method + ':' + server.password + '@' + serverInfo.address + ':' + account.port).toString('base64');
return serverInfo;
});
});
const serverReturn = await Promise.all(serverList);
const data = {
default: {
site: (config.plugins.macAccount ? config.plugins.macAccount.site : null) || config.plugins.webgui.site,
id: server.id,
name: server.name,
address,
port: account.port + server.shift,
password: account.password,
method: server.method,
comment: server.comment,
},
servers: serverReturn,
};
if(noPassword) {
delete data.default.password;
}
if(!serverReturn.filter(f => f.name === server.name)[0]) {
data.default.name = serverReturn[0].name;
data.default.address = serverReturn[0].address;
}
return data;
};
const editAccount = (id, mac, serverId, accountId) => {
return knex('mac_account').update({
mac, serverId, accountId,
}).where({ id });
};
const deleteAccount = id => {
return knex('mac_account').delete().where({ id }).then();
};
const login = async (mac, ip) => {
if(scanLoginLog(ip)) {
return Promise.reject('ip is in black list');
}
const account = await knex('mac_account').where({
mac: formatMacAddress(mac)
}).then(success => success[0]);
if(!account) {
loginFail(mac, ip);
return Promise.reject('mac account not found');
} else {
return account;
}
};
const getAccountByAccountId = accountId => {
return knex('mac_account').where({
accountId
});
};
const getAllAccount = async group => {
const where = {};
if(group >= 0) {
where['user.group'] = group;
}
const accounts = await knex('mac_account').select([
'mac_account.id as id',
'mac_account.mac as mac',
'mac_account.userId as userId',
'mac_account.accountId as accountId',
'mac_account.serverId as serverId',
'account_plugin.port as port',
]).leftJoin('account_plugin', 'mac_account.accountId', 'account_plugin.id')
.leftJoin('user', 'user.id', 'mac_account.userId')
.where(where);
return accounts;
};
const getAccountByUserId = userId => {
return knex('mac_account').where({
userId
});
};
const removeInvalidMacAccount = async () => {
const accounts = await knex('mac_account').select([
'mac_account.id as id',
'mac_account.mac as mac',
'mac_account.userId as userId',
'mac_account.accountId as accountId',
'mac_account.serverId as serverId',
'user.username as username',
]).leftJoin('user', 'mac_account.userId', 'user.id')
.where({});
accounts.filter(f => {
return f.username === null;
}).forEach(account => {
knex('mac_account').where({ id: account.id }).del().then();
});
};
removeInvalidMacAccount();
const isMacAddress = mac => mac.match(/^[0-9,a-f]{12}$/);
const userAddMacAccount = async (userId, mac) => {
const macAddress = formatMacAddress(mac);
if(!isMacAddress(macAddress)) { return Promise.reject(); }
const currentMacAccount = await knex('mac_account').where({ userId });
const insertData = {
mac: macAddress,
userId,
};
if(currentMacAccount.length) { return Promise.reject(); }
const userAccount = await knex('account_plugin').where({ userId });
if(userAccount.length) {
insertData.accountId = userAccount[0].id;
}
await knex('mac_account').insert(insertData);
return;
};
exports.editAccount = editAccount;
exports.newAccount = newAccount;
exports.getAccount = getAccount;
exports.deleteAccount = deleteAccount;
exports.getAccountForUser = getAccountForUser;
exports.getNoticeForUser = getNoticeForUser;
exports.login = login;
exports.getAccountByAccountId = getAccountByAccountId;
exports.getAllAccount = getAllAccount;
exports.getAccountByUserId = getAccountByUserId;
exports.userAddMacAccount = userAddMacAccount;