Add permissions and few other fixes

This commit is contained in:
danbulant 2020-04-02 15:59:36 +02:00
parent 1b519708bc
commit e4aacc4351
23 changed files with 314 additions and 102 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,18 +0,0 @@
version: 2
jobs:
build:
parallelism: 2
docker:
- image: circleci/node:10
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run: npm i
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run: npm run lint
- run: npm test

4
app.js Normal file → Executable file
View file

@ -22,4 +22,6 @@ module.exports = mcServer.createMCServer(settings)
process.on('unhandledRejection', err => {
console.log(err.stack)
})
});
console.log("MC Server running as " + process.pid);

1
config/permissions.json Normal file
View file

@ -0,0 +1 @@
{"groups":{"default":{"perms":["world.*","chat.*", "info.*"],"noPerms":["op.*"]}, "admin":{"noPerms": [], "perms": ["*.*"], "prefix": "&4[Admin]&r"}},"players":{"TechmandanCZ": {"group":"admin"}}}

25
config/translation.json Normal file
View file

@ -0,0 +1,25 @@
{
"server": {
"disconnected": "&ePlayer %1 disconnected",
"disconnectedPrefix": true,
"particles": "Emitting %1 particles (count: %2, size: %3)"
},
"defaults": {
"ban": "You've been banned from this server!",
"banIp": "Your IP have been banned from this server!"
},
"commands": {
"ban": "You were banned from this server!",
"banB": "Player %1 was banned from this server!",
"kick": "You were kicked from this server!",
"kickB": "Player %1 was kicked!"
},
"errors": {
"perms": "You don't have permission to do that!",
"maxParticle": "You can't spawn more than %1 particles",
"playerExists": "Player %1 is not on this server"
},
"info": {
"particle": "Emit a particle at position"
}
}

View file

@ -1,22 +0,0 @@
const mcServer = require('flying-squid')
mcServer.createMCServer({
'motd': 'A Minecraft Server \nRunning flying-squid',
'port': 25565,
'max-players': 10,
'online-mode': true,
'logging': true,
'gameMode': 1,
'generation': {
'name': 'diamond_square',
'options': {
'worldHeight': 80
}
},
'kickTimeout': 10000,
'plugins': {
},
'modpe': false,
'view-distance': 10
})

View file

@ -1,14 +1,15 @@
const mc = require('minecraft-protocol')
const EventEmitter = require('events').EventEmitter
const path = require('path')
const requireIndex = require('./lib/requireindex')
const supportedVersions = require('./lib/version').supportedVersions
require('emit-then').register()
const mc = require('minecraft-protocol');
const EventEmitter = require('events').EventEmitter;
const path = require('path');
const requireIndex = require('./lib/requireindex');
const supportedVersions = require('./lib/version').supportedVersions;
require('emit-then').register();
if (process.env.NODE_ENV === 'dev') {
require('longjohn')
}
const supportFeature = require('./lib/supportFeature')
const supportFeature = require('./lib/supportFeature');
module.exports = {
createMCServer: createMCServer,
@ -19,36 +20,40 @@ module.exports = {
UserError: require('./lib/user_error'),
portal_detector: require('./lib/portal_detector'),
supportedVersions
}
};
function createMCServer (options) {
options = options || {}
const mcServer = new MCServer()
mcServer.connect(options)
return mcServer
options = options || {};
const mcServer = new MCServer();
mcServer.connect(options);
return mcServer;
}
class MCServer extends EventEmitter {
constructor () {
super()
this._server = null
super();
this._server = null;
}
connect (options) {
const version = require('minecraft-data')(options.version).version
const version = require('minecraft-data')(options.version).version;
if (supportedVersions.indexOf(version.majorVersion) === -1) {
throw new Error(`Version ${version.minecraftVersion} is not supported.`)
throw new Error(`Version ${version.minecraftVersion} is not supported.`);
}
this.supportFeature = feature => supportFeature(feature, version.majorVersion)
this.supportFeature = feature => supportFeature(feature, version.majorVersion);
const plugins = requireIndex(path.join(__dirname, 'lib', 'plugins'))
const plugins = requireIndex(path.join(__dirname, 'lib', 'plugins'));
this._server = mc.createServer(options)
Object.keys(plugins)
.filter(pluginName => plugins[pluginName].server !== undefined)
.forEach(pluginName => plugins[pluginName].server(this, options))
if (options.logging === true) this.createLog()
this._server.on('error', error => this.emit('error', error))
this._server.on('listening', () => this.emit('listening', this._server.socketServer.address().port))
this.emit('asap')
.forEach(pluginName => plugins[pluginName].server(this, options));
if (options.logging === true) this.createLog();
this._server.on('error', error => this.emit('error', error));
this._server.on('listening', () => this.emit('listening', this._server.socketServer.address().port));
this.emit('init')
}
}

View file

@ -1,3 +1,5 @@
const permissions = require("./plugins/permissions").permissions();
class Command {
constructor (params, parent, hash) {
this.params = params
@ -18,18 +20,19 @@ class Command {
return undefined
}
async use (command, op = true) {
async use (command, op = true, username = null) {
let res = this.find(command)
if (res) {
let [com, pars] = res
if (com.params.op && !op) return 'You do not have permission to use this command'
if (!op && com.params.permission && !permissions.hasPermission(username, com.params.permission)) return 'You do not have permission to use this command';
if (com.params.op && !op) return 'You do not have permission to use this command';
const parse = com.params.parse
if (parse) {
if (typeof parse === 'function') {
pars = parse(pars)
if (pars === false) {
return com.params.usage ? 'Usage: ' + com.params.usage : 'Bad syntax'
return com.params.usage ? 'Usage: ' + com.params.usage : 'Bad syntax';
}
} else {
pars = pars.match(parse)

View file

@ -1,3 +1,5 @@
const permissions = require("./permissions").permissions();
module.exports.server = function (serv) {
serv.broadcast = (message, { whitelist = serv.players, blacklist = [], system = false } = {}) => {
if (whitelist.type === 'player') whitelist = [whitelist]
@ -122,11 +124,15 @@ module.exports.server = function (serv) {
module.exports.player = function (player, serv) {
player._client.on('chat', ({ message } = {}) => {
if (message[0] === '/') {
if(!permissions.hasPermission(player.username, "commands.use"))return player.chat("You don't have permission to use commands");
player.behavior('command', { command: message.slice(1) }, ({ command }) => player.handleCommand(command))
} else {
if(!permissions.hasPermission(player.username, "chat.send"))return player.chat("You don't have permission to use chat");
player.behavior('chat', {
message: message,
prefix: '<' + player.username + '> ',
prefix: permissions.getPrefix(player.username) + player.username + permissions.getSuffix(player.username) + permissions.getSeparator(),
text: message,
whitelist: serv.players,
blacklist: []

View file

@ -1,10 +1,12 @@
const UserError = require('flying-squid').UserError
const permissions = require("./permissions");
module.exports.player = function (player, serv, { version }) {
player.commands.add({
base: 'help',
info: 'to show all commands',
usage: '/help [command]',
permission: "commands.help",
parse (str) {
const params = str.split(' ')
const page = parseInt(params[params.length - 1])
@ -24,7 +26,7 @@ module.exports.player = function (player, serv, { version }) {
if (found.length === 0) { // None found
return 'Could not find any matches'
} else if (found.length === 1) { // Single command found, giev info on command
} else if (found.length === 1) { // Single command found, give info on command
const cmd = hash[found[0]]
const usage = (cmd.params && cmd.params.usage) || cmd.base
const info = (cmd.params && cmd.params.info) || 'No info'
@ -69,6 +71,7 @@ module.exports.player = function (player, serv, { version }) {
base: 'modpe',
info: 'for modpe commands',
usage: '/modpe <params>',
permissions: "mod.pe",
parse (str) { return str || false },
action (str) {
player.emit('modpe', str)
@ -79,8 +82,9 @@ module.exports.player = function (player, serv, { version }) {
base: 'version',
info: 'to get version of the server',
usage: '/version',
permissions: "info.version",
action () {
return 'This server is running flying-squid version ' + version
return 'This server is running nodecraft version ' + version
}
})
@ -88,8 +92,9 @@ module.exports.player = function (player, serv, { version }) {
base: 'bug',
info: 'to bug report',
usage: '/bug',
permissions: "info.bug",
action () {
return 'Report bugs / issues here: https://github.com/PrismarineJS/flying-squid/issues'
return 'Report bugs / issues here: https://github.com/PrismarineJS/flying-squid/issues unless related to permissions, in which case contact developer TechamdanCZ#0135 on discord.'
}
})
@ -109,7 +114,7 @@ module.exports.player = function (player, serv, { version }) {
player.handleCommand = async (str) => {
try {
const res = await player.commands.use(str, player.op)
const res = await player.commands.use(str, player.op, player.username)
if (res) player.chat(serv.color.red + res)
} catch (err) {
if (err.userError) player.chat(serv.color.red + 'Error: ' + err.message)

View file

@ -1,4 +1,5 @@
const Vec3 = require('vec3').Vec3
const permissions = require("./permissions").permissions();
module.exports.player = function (player, serv) {
function cancelDig ({ position, block }) {
@ -6,6 +7,7 @@ module.exports.player = function (player, serv) {
}
player._client.on('block_dig', async ({ location, status, face }) => {
if(!permissions.hasPermission(player.username, "world.dig"))return;
let pos = new Vec3(location.x, location.y, location.z)
const directionVector = directionToVector[face]

View file

@ -39,7 +39,7 @@ module.exports.server = function (serv, settings) {
if (serv.plugins[p].server) f.call(serv.plugins[p], serv, settings)
})
serv.on('asap', () => {
serv.on('ready', () => {
Object.keys(serv.plugins).map(p => serv.log('[PLUGINS] Loaded "' + serv.plugins[p].name + '"'))
})

View file

@ -1,4 +1,6 @@
const once = require('event-promise')
const permissions = require("./permissions").permissions();
const translation = require("./translation").translation();
module.exports.server = function (serv) {
serv.quit = async (reason = 'Going down') => {
@ -18,7 +20,10 @@ module.exports.player = function (player, serv) {
player._client.on('end', () => {
if (player && player.username) {
serv.broadcast(serv.color.yellow + player.username + ' quit the game.')
var user = player.username;
if(translation.server.disconnectedPrefix) user = permissions.getPrefix(player.username) + player.username + permissions.getSuffix(player.username);
serv.broadcast(translation.server.disconnected.replace("%1", user));
player._writeOthers('player_info', {
action: 4,
data: [{

View file

@ -1,18 +1,19 @@
const moment = require('moment')
const rp = require('request-promise')
const UUID = require('uuid-1345')
const translation = require("./translation").translation();
module.exports.server = function (serv) {
serv.ban = (uuid, reason) => {
serv.bannedPlayers[uuid] = {
time: +moment(),
reason: reason || 'Your account is banned!'
reason: reason || translation.defaults.ban
}
}
serv.banIP = (IP, reason) => {
serv.bannedIPs[IP] = {
time: +moment(),
reason: reason || 'Your IP is banned!'
reason: reason || translation.defaults.banIp
}
Object.keys(serv.players)
.filter(uuid => serv.players[uuid]._client.socket.remoteAddress === IP)
@ -59,17 +60,17 @@ module.exports.server = function (serv) {
}
module.exports.player = function (player, serv) {
player.kick = (reason = 'You were kicked!') =>
player._client.end(reason)
player.kick = (reason) =>
player._client.end(reason || translation.commands.kick)
player.ban = reason => {
reason = reason || 'You were banned!'
reason = reason || translation.commands.ban
player.kick(reason)
const uuid = player.uuid
serv.ban(uuid, reason)
}
player.banIP = reason => {
reason = reason || 'You were IP banned!'
reason = reason || translation.commands.banIp
player.kick(reason)
serv.banIP(player._client.socket.remoteAddress)
}
@ -80,7 +81,7 @@ module.exports.player = function (player, serv) {
base: 'kick',
info: 'to kick a player',
usage: '/kick <player> [reason]',
op: true,
permissions: "commands.kick",
parse (str) {
if (!str.match(/([a-zA-Z0-9_]+)(?: (.*))?/)) { return false }
const parts = str.split(' ')
@ -92,7 +93,7 @@ module.exports.player = function (player, serv) {
action ({ username, reason }) {
const kickPlayer = serv.getPlayer(username)
if (!kickPlayer) {
player.chat(username + ' is not on this server!')
player.chat(translation.errors.playerExists.replace("%1", username))
} else {
kickPlayer.kick(reason)
kickPlayer.emit('kicked', player, reason)
@ -104,7 +105,7 @@ module.exports.player = function (player, serv) {
base: 'ban',
info: 'to ban a player',
usage: '/ban <player> [reason]',
op: true,
permissions: "commands.ban",
parse (str) {
if (!str.match(/([a-zA-Z0-9_]+)(?: (.*))?/)) { return false }
const parts = str.split(' ')
@ -120,11 +121,11 @@ module.exports.player = function (player, serv) {
serv.banUsername(username, reason)
.then(() => {
serv.emit('banned', player, username, reason)
player.chat(username + ' was banned')
player.chat(translation.commands.banB.replace("%1",username))
})
.catch(err => {
if (err) { // This tricks eslint
player.chat(username + ' is not a valid player!')
player.chat(translation.errros.playerExists.replace("%1",username))
}
})
} else {
@ -138,7 +139,7 @@ module.exports.player = function (player, serv) {
base: 'ban-ip',
info: 'bans a specific IP',
usage: '/ban-ip <ip> [reason]',
op: true,
permissions: "commands.banip",
parse (str) {
const argv = str.split(' ')
if (argv.length < 1) return
@ -158,7 +159,7 @@ module.exports.player = function (player, serv) {
base: 'pardon-ip',
info: 'to pardon a player by ip',
usage: '/pardon-ip <ip>',
op: true,
permissions: "commands.pardon",
action (IP) {
const result = serv.pardonIP(IP)
player.chat(result ? IP + ' was IP pardoned' : IP + ' is not banned')
@ -169,7 +170,7 @@ module.exports.player = function (player, serv) {
base: 'pardon',
info: 'to pardon a player',
usage: '/pardon <player>',
op: true,
permissions: "commands.pardon",
parse (str) {
if (!str.match(/([a-zA-Z0-9_]+)/)) { return false }
return str

View file

@ -1,4 +1,5 @@
const Vec3 = require('vec3').Vec3
const translation = require("./translation").translation();
module.exports.server = function (serv) {
serv.emitParticle = (particle, world, position, { whitelist, blacklist = [], radius = 32, longDistance = true, size = new Vec3(1, 1, 1), count = 1 } = {}) => {
@ -27,9 +28,9 @@ module.exports.server = function (serv) {
module.exports.player = function (player, serv) {
player.commands.add({
base: 'particle',
info: 'emit a particle at a position',
info: translation.info.particle,
usage: '/particle <id> [amount] [<sizeX> <sizeY> <sizeZ>]',
op: true,
permission: "world.emitParticle",
parse (str) {
const results = str.match(/(\d+)(?: (\d+))?(?: (\d+))?(?: (\d+))?(?: (\d+))?(?: (\d+))?/)
if (!results) return false
@ -41,10 +42,10 @@ module.exports.player = function (player, serv) {
},
action ({ particle, amount, size }) {
if (amount >= 100000) {
player.chat('You cannot emit more than 100,000 particles!')
player.chat(translation.errors.maxParticle)
return
}
player.chat('Emitting "' + particle + '" (count: ' + amount + ', size: ' + size.toString() + ')')
player.chat(translation.server.particles.replace("%1", particle).replace("%2", amount).replace("%3", size.toString()));
serv.emitParticle(particle, player.world, player.position, { count: amount, size: size })
}
})

View file

@ -0,0 +1,152 @@
const fs = require('fs');
module.exports.player = (player, serv)=>{
player.commands.add({
base: "perms",
info: "Permission commands",
usage: "/perms ?",
permissions: "commands.permissions",
action (cmd) {
var c = cmd.substr(cmd.indexOf(" ") == -1 ? 0 : cmd.indexOf(" "));
switch(c){
case "?":
case "h":
case "help":
player.chat("Help to be done");
break;
default:
return "Command couldn't be found. Try /perms ?"
}
}
});
player.commands.add({
base: "broadcast",
info: "Broadcast message",
usage: "/broadcast <message>",
permission: "commands.broadcast",
action(cmd){
serv.broadcast(cmd);
}
})
}
module.exports.permissions = ()=>{
if(!fs.existsSync(__dirname + "/../../../config/permissions.json")){
console.log("Permission config file doesn't exist, creating one");
var defaultPerms = {
groups: {
default: {
perms: ["world.*", "chat.*", "info.*"],
noPerms: ["world.difficulty", "world.changeGamemode"]
}
},
players: {}
}
fs.writeFileSync(__dirname + "/../../../config/permissions.json", JSON.stringify(defaultPerms, null, 2));
}
global.permissions = JSON.parse(fs.readFileSync(__dirname + "/../../../config/permissions.json", 'utf8'));
var permissions = {};
permissions.getSeparator = ()=>"> ";
permissions._getProperty = (player, property)=>{
var group = "default";
var perms = global.permissions;
if(perms.players[player]){
if(perms.players[player].group)group = perms.players[player].group;
if(perms.players[player][property])return perms.players[player][property];
}
if(!perms.groups[group]){
if(perms.groups.default)group = "default";
else throw Error("No default permission group. Cannot continue.");
}
return perms.groups[group][property] | "";
}
permissions.getPrefix = (player)=>{
return permissions._getProperty(player, "prefix");
}
permissions.getSuffix = (player)=>{
return permissions._getProperty(player, "suffix");
}
permissions.hasPermission = (player, permission)=>{
var permArr = permission.split(".");
var group = "default";
var perms = global.permissions;
if(!perms.players)perms.players = {};
if(!perms.groups)perms.groups = {};
if(perms.players[player]){
if(perms.players[player].group)group = perms.players[player].group;
}
if(!perms.groups[group]){
if(perms.groups.default)group = "default";
else throw Error("No default permission group. Cannot continue.");
}
var allowed = perms.groups[group].perms || [];
var hasPermission = false;
if(!Array.isArray(allowed)){
console.warn("perms in group " + group + " is of wrong type");
console.log(perms);
console.log(perms.groups[group]);
console.log(perms.groups[group].perms);
console.log(allowed);
allowed = [];
}
for(var perm of allowed){
var p = perm.split(".");
var isSame = true;
for(var i in p){
if(p[i] != permArr[i] && p[i] != "*"){
isSame = false;
break;
}
}
if(isSame){
hasPermission = true;
break;
}
}
var disallowed = perms.groups[group].noPerms || [];
if(!Array.isArray(disallowed)){
console.warn("noperms in group " + group + " is of wrong type");
disallowed = [];
}
for(var perm of disallowed){
var p = perm.split(".");
var isSame = true;
for(var i in p){
if(p[i] != permArr[i] && p[i] != "*"){
isSame = false;
break;
}
}
if(isSame){
hasPermission = false;
break;
}
}
return hasPermission;
}
permissions.loadState = ()=>{
global.permissions = fs.readFileSync(__dirname + "/../../../config/permissions.json", 'utf8');
return global.permissions;
}
permissions.saveState = ()=>{
return fs.writeSync(__dirname + "/../../../config/permissions.json", JSON.stringify(global.permission));
}
return permissions;
}

View file

@ -1,5 +1,5 @@
const Vec3 = require('vec3').Vec3
const permissions = require("./permissions").permissions();
const materialToSound = {
undefined: 'stone',
'rock': 'stone',
@ -14,6 +14,8 @@ module.exports.player = function (player, serv, { version }) {
const blocks = require('minecraft-data')(version).blocks
player._client.on('block_place', ({ direction, location } = {}) => {
if(!permissions.hasPermission(player.username, "world.place"))return;
const heldItem = player.inventory.slots[36 + player.heldItemSlot]
if (heldItem === undefined) return
if (direction === -1 || heldItem.type === -1 || !blocks[heldItem.type]) return

View file

@ -17,7 +17,7 @@ module.exports.player = function (player, serv) {
aliases: ['gm'],
info: 'to change game mode',
usage: '/gamemode <0-3>',
op: true,
permission: 'world.changeGamemode',
parse (str) {
if (!str.match(/^([0-3])$/)) { return false }
return parseInt(str)
@ -32,7 +32,7 @@ module.exports.player = function (player, serv) {
aliases: ['diff'],
info: 'Sets the difficulty level',
usage: '/difficulty <difficulty>',
op: true,
permission: "world.difficulty",
parse (str) {
if (!str.match(/^([0-3])$/)) { return false }
return parseInt(str)

View file

@ -34,7 +34,7 @@ module.exports.player = function (player, serv) {
base: 'kill',
info: 'Kill entities',
usage: '/kill <selector>',
op: true,
permission: "mod.kill",
parse (str) {
return str || false
},

View file

@ -84,7 +84,7 @@ module.exports.player = function (player, serv, options) {
base: 'summon',
info: 'Summon an entity',
usage: '/summon <entity_name>',
op: true,
permission: "world.summon",
action (name) {
if (Object.keys(serv.entities).length > options['max-entities']) { throw new UserError('Too many mobs !') }
const entity = entitiesByName[name]
@ -108,7 +108,7 @@ module.exports.player = function (player, serv, options) {
base: 'summonMany',
info: 'Summon many entities',
usage: '/summonMany <number> <entity_name>',
op: true,
permission: "world.summonMany",
parse (str) {
const args = str.split(' ')
if (args.length !== 2) { return false }
@ -140,7 +140,7 @@ module.exports.player = function (player, serv, options) {
base: 'pile',
info: 'make a pile of entities',
usage: '/pile <entities types>',
op: true,
permission: "world.pile",
parse (str) {
const args = str.split(' ')
if (args.length === 0) { return false }
@ -172,7 +172,7 @@ module.exports.player = function (player, serv, options) {
base: 'attach',
info: 'attach an entity on an other entity',
usage: '/attach <carrier> <attached>',
op: true,
permission: "world.attach",
parse (str) {
const args = str.split(' ')
if (args.length !== 2) { return false }

View file

@ -7,7 +7,7 @@ module.exports.player = (player, serv) => {
aliases: ['tp'],
info: 'to teleport a player',
usage: '/teleport [target player] <destination player or x> [y] [z]',
op: true,
permission: "mod.tp",
parse (str) {
return str.match(/^(((.* )?~?-?\d* ~?-?\d* ~?-?\d*)|(.+ .+))$/) ? str.split(' ') : false
},

View file

@ -0,0 +1,42 @@
const fs = require("fs");
module.exports = {};
module.exports.translation = ()=>{
if(global.trans)return global.trans;
var trans = {
server: {
disconnected: "&ePlayer %1 disconnected",
disconnectedPrefix: true,
particles: "Emitting %1 particles (count: %2, size: %3)"
},
defaults: {
ban: "You've been banned from this server!",
banIp: "Your IP have been banned from this server!",
},
commands: {
ban: "You were banned from this server!",
banB: "Player %1 was banned from this server!",
kick: "You were kicked from this server!",
kickB: "Player %1 was kicked!"
},
errors: {
perms: "You don't have permission to do that!",
maxParticle: "You can't spawn more than %1 particles",
playerExists: "Player %1 is not on this server"
},
info: {
particle: "Emit a particle at position"
}
};
if(!fs.existsSync(__dirname + "/../../../config/translation.json")){
fs.writeFileSync(__dirname + "/../../../config/translation.json", JSON.stringify(trans, null, 2));
} else {
trans = JSON.parse(fs.readFileSync(__dirname + "/../../../config/translation.json", "utf8"));
}
global.trans = trans;
return trans;
}

View file

@ -3,7 +3,7 @@ module.exports.player = function (player, serv) {
base: 'weather',
info: 'Sets the weather.',
usage: '/weather <clear|rain>',
op: true,
permission: "world.changeWeather",
parse (str) {
const args = str.split(' ')
if (args.length !== 1) { return false }