mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Fix module names, which are incorrectly deduced by JSDoc, and references to master, slave and ctl modules that are now in separate packages.
470 lines
12 KiB
JavaScript
470 lines
12 KiB
JavaScript
/**
|
|
* User and permissions library
|
|
*
|
|
* Defines data types for users, roles and permissions.
|
|
*
|
|
* @author Hornwitser
|
|
* @module lib/users
|
|
*/
|
|
"use strict";
|
|
const jwt = require("jsonwebtoken");
|
|
|
|
/**
|
|
* Represents a permission that can be granted
|
|
* @static
|
|
*/
|
|
class Permission {
|
|
constructor(name, title, description, grantByDefault) {
|
|
this.name = name;
|
|
this.title = title;
|
|
this.description = description;
|
|
this.grantByDefault = grantByDefault;
|
|
}
|
|
}
|
|
const permissions = new Map();
|
|
|
|
/**
|
|
* Define a new user permission for Clusterio
|
|
*
|
|
* Defines a permission for use in Clusterio. Defined permissions can be
|
|
* granted to roles, and checked on users. Name should be `lower_case`
|
|
* style and start with the plugin name followed by a dot.
|
|
*
|
|
* @param {Object} def - The definition for the permission.
|
|
* @param {string} def.name - The internal name for the permission.
|
|
* @param {string} def.title - User facing name for permission.
|
|
* @param {string} def.description - User facing description.
|
|
* @param {boolean} def.grantByDefault -
|
|
* If true this role is granted by the generated Player role by default.
|
|
*
|
|
*/
|
|
function definePermission({ name, title, description, grantByDefault=false }) {
|
|
if (typeof name !== "string" || !name.length) {
|
|
throw new Error("Expected name to be a non-empty string");
|
|
}
|
|
if (typeof title !== "string" || !title.length) {
|
|
throw new Error("Expected title to be a non-empty string");
|
|
}
|
|
if (typeof description !== "string" || !description.length) {
|
|
throw new Error("Expected description to be a non-empty string");
|
|
}
|
|
if (typeof grantByDefault !== "boolean") {
|
|
throw new Error("Expected grantByDefault to be a boolean");
|
|
}
|
|
|
|
if (permissions.has(name)) {
|
|
throw new Error(`Permission '${name}' is already defined`);
|
|
}
|
|
|
|
permissions.set(name, new Permission(name, title, description, grantByDefault));
|
|
}
|
|
|
|
/**
|
|
* Represents a collection of granted permissions
|
|
* @static
|
|
*/
|
|
class Role {
|
|
constructor({ id, name, description, permissions }) {
|
|
this.id = id;
|
|
this.name = name;
|
|
this.description = description;
|
|
this.permissions = new Set(permissions);
|
|
}
|
|
|
|
serialize() {
|
|
return {
|
|
id: this.id,
|
|
name: this.name,
|
|
description: this.description,
|
|
permissions: [...this.permissions],
|
|
};
|
|
}
|
|
|
|
grantDefaultPermissions() {
|
|
for (let permission of permissions.values()) {
|
|
if (permission.grantByDefault) {
|
|
this.permissions.add(permission.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Ensure the default admin role has full access
|
|
*
|
|
* Ensures the role with id 0 exisits and has the core.admin permission.
|
|
*
|
|
* @param {Map<number, module:lib/user.Role>} roles - role storage to modify.
|
|
* @returns {module:lib/permission.Role} the default admin role
|
|
* @static
|
|
*/
|
|
function ensureDefaultAdminRole(roles) {
|
|
let admin = roles.get(0);
|
|
if (!admin) {
|
|
admin = new Role({ id: 0, name: "Admin", description: "Cluster wide administrator." });
|
|
roles.set(0, admin);
|
|
}
|
|
admin.permissions.add("core.admin");
|
|
return admin;
|
|
}
|
|
|
|
/**
|
|
* Ensure the default player role has default accesses
|
|
*
|
|
* Ensures the role with id 1 exists and has all the permissions that are
|
|
* assigned by default. Note that this may not be the same role as the one
|
|
* that is assigned to new users by default.
|
|
*
|
|
* @param {Map<number, module:lib/user.Role>} roles - role storage to modify.
|
|
* @static
|
|
*/
|
|
function ensureDefaultPlayerRole(roles) {
|
|
let player = roles.get(1);
|
|
if (!player) {
|
|
player = new Role({ id: 1, name: "Player", description: "Default player role." });
|
|
roles.set(1, player);
|
|
}
|
|
player.grantDefaultPermissions();
|
|
}
|
|
|
|
|
|
/**
|
|
* Represeents a user in the cluster
|
|
*
|
|
* Holds data about a Factorio user in the cluster.
|
|
* @static
|
|
*/
|
|
class User {
|
|
constructor(serializedUser, loadedRoles) {
|
|
/**
|
|
* Factorio user name.
|
|
* @type {string}
|
|
*/
|
|
this.name = serializedUser.name;
|
|
|
|
/**
|
|
* Instances this user is online on.
|
|
* @type {Set<number>}
|
|
*/
|
|
this.instances = new Set();
|
|
|
|
/**
|
|
* Unix time in seconds the user token must be issued after to be valid.
|
|
* @type {number}
|
|
*/
|
|
this.tokenValidAfter = serializedUser.token_valid_after || 0;
|
|
|
|
/**
|
|
* True if the user is promoted to admin on the Factorio instances.
|
|
* @type {boolean}
|
|
*/
|
|
this.isAdmin = Boolean(serializedUser.is_admin);
|
|
|
|
/**
|
|
* True if the user is whitelisted on the Factorio instances.
|
|
* @type {boolean}
|
|
*/
|
|
this.isWhitelisted = Boolean(serializedUser.is_whitelisted);
|
|
|
|
/**
|
|
* True if the user is banned from Factorio instances.
|
|
* @type {boolean}
|
|
*/
|
|
this.isBanned = Boolean(serializedUser.is_banned);
|
|
|
|
/**
|
|
* Reason for being banned. Ignored if isBanned is false.
|
|
* @type {string}
|
|
*/
|
|
this.banReason = serializedUser.ban_reason || "";
|
|
|
|
/**
|
|
* Roles this user has
|
|
* @type {Set<module:lib/permissions.Role>}
|
|
*/
|
|
this.roles = new Set();
|
|
if (serializedUser.roles) {
|
|
for (let roleId of serializedUser.roles) {
|
|
let role = loadedRoles.get(roleId);
|
|
if (role) {
|
|
this.roles.add(role);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
serialize() {
|
|
let serialized = {
|
|
name: this.name,
|
|
};
|
|
|
|
if (this.roles.size) {
|
|
serialized.roles = [...this.roles].map(role => role.id);
|
|
}
|
|
|
|
if (this.tokenValidAfter) {
|
|
serialized.token_valid_after = this.tokenValidAfter;
|
|
}
|
|
|
|
if (this.isAdmin) {
|
|
serialized.is_admin = true;
|
|
}
|
|
|
|
if (this.isWhitelisted) {
|
|
serialized.is_whitelisted = true;
|
|
}
|
|
|
|
if (this.isBanned) {
|
|
serialized.is_banned = true;
|
|
}
|
|
|
|
if (this.banReason) {
|
|
serialized.ban_reason = this.banReason;
|
|
}
|
|
|
|
return serialized;
|
|
}
|
|
|
|
/**
|
|
* Generate access token for this user
|
|
*
|
|
* @param {string} secret - Secret to sign token with.
|
|
* @returns {string} JWT access token for the user.
|
|
*/
|
|
createToken(secret) {
|
|
return jwt.sign({ aud: "user", user: this.name }, secret);
|
|
}
|
|
|
|
/**
|
|
* Invalidate current tokens for the user
|
|
*
|
|
* Sets the tokenValidAfter property to the current time, which causes
|
|
* all currently issued tokens for the user to become invalid.
|
|
*/
|
|
invalidateToken() {
|
|
this.tokenValidAfter = Math.floor(Date.now() / 1000);
|
|
}
|
|
|
|
/**
|
|
* Check if a given permission is granted
|
|
*
|
|
* Checks the roles the user is member of for one that grants the given
|
|
* permission. If the permission is not granted for the user a
|
|
* "Permission denied" error is thrown.
|
|
*
|
|
* @param {string} permission - The permission to check for.
|
|
* @throws {Error} if the user does noh have the given permission.
|
|
*/
|
|
checkPermission(permission) {
|
|
if (!permissions.has(permission)) {
|
|
throw new Error(`permission ${permission} does not exist`);
|
|
}
|
|
|
|
for (let role of this.roles) {
|
|
if (role.permissions.has("core.admin") || role.permissions.has(permission)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new Error("Permission denied");
|
|
}
|
|
|
|
notifyJoin(instance_id) {
|
|
this.instances.add(instance_id);
|
|
User.onlineUsers.add(this);
|
|
}
|
|
|
|
notifyLeave(instance_id) {
|
|
this.instances.delete(instance_id);
|
|
if (!this.instances.size) {
|
|
User.onlineUsers.delete(this);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Set of users currently online in the cluster.
|
|
* @type {module:lib/users.User}
|
|
*/
|
|
User.onlineUsers = new Set();
|
|
|
|
// Definitions for the built in permissions used in Clusterio.
|
|
// description should answer "this permission allows you to ___"
|
|
definePermission({
|
|
name: "core.admin",
|
|
title: "Administrator",
|
|
description: "Bypass all permission checks.",
|
|
});
|
|
definePermission({
|
|
name: "core.control.connect",
|
|
title: "Connect to WebSocket",
|
|
description: "Connect to the master's WebSocket control interface.",
|
|
grantByDefault: true,
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.slave.list",
|
|
title: "List slaves",
|
|
description: "Get the full list of slaves in the cluster.",
|
|
grantByDefault: true,
|
|
});
|
|
definePermission({
|
|
name: "core.slave.generate_token",
|
|
title: "Generate slave token",
|
|
description: "Generate tokens for slaves to connect to the cluster with.",
|
|
});
|
|
definePermission({
|
|
name: "core.slave.create_config",
|
|
title: "Create slave config",
|
|
description: "Create slave configs via the master server.",
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.instance.list",
|
|
title: "List instances",
|
|
description: "Get the full list of instances in the cluster.",
|
|
grantByDefault: true,
|
|
});
|
|
definePermission({
|
|
name: "core.instance.create",
|
|
title: "Create instance",
|
|
description: "Create new instances on the master server.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.get_config",
|
|
title: "Get instance config",
|
|
description: "Get the config of instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.update_config",
|
|
title: "Modify instance config",
|
|
description: "Modify the config or entries of the config for instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.assign",
|
|
title: "Assign instance",
|
|
description: "Assign or reassign instances to a slave.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.follow_log",
|
|
title: "Follow instance log",
|
|
description: "Listen for the stdout log of instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.create_save",
|
|
title: "Create new instance save",
|
|
description: "Create new savegames on instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.export_data",
|
|
title: "Export locale and icons",
|
|
description: "Export the the locale and icons from an instance and upload it to the master.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.start",
|
|
title: "Start instance",
|
|
description: "Start instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.load_scenario",
|
|
title: "Load scenario",
|
|
description: "Start instances by loading a scenario.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.stop",
|
|
title: "Stop instance",
|
|
description: "Stop instances.",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.delete",
|
|
title: "Delete instance",
|
|
description: "Delete instances (includes config and all files stored like saves and logs).",
|
|
});
|
|
definePermission({
|
|
name: "core.instance.send_rcon",
|
|
title: "Send RCON",
|
|
description: "Send arbitrary RCON commands to instances.",
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.permission.list",
|
|
title: "List permissions",
|
|
description: "Get the full list of permissions in the cluster.",
|
|
grantByDefault: true,
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.role.list",
|
|
title: "List roles",
|
|
description: "Get the full list of roles and their permissions in the cluster.",
|
|
grantByDefault: true,
|
|
});
|
|
definePermission({
|
|
name: "core.role.create",
|
|
title: "Create role",
|
|
description: "Create new roles with permissions.",
|
|
});
|
|
definePermission({
|
|
name: "core.role.update",
|
|
title: "Update role",
|
|
description: "Edit existing roles including permissions they grant.",
|
|
});
|
|
definePermission({
|
|
name: "core.role.delete",
|
|
title: "Delete role",
|
|
description: "Permanently delete roles.",
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.user.list",
|
|
title: "List users",
|
|
description: "Get the full list of users in the cluster.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.create",
|
|
title: "Create users",
|
|
description: "Create user acounts with a given name.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.update_roles",
|
|
title: "Update user roles",
|
|
description: "Add or remove any role to any user.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.set_admin",
|
|
title: "Set user admin status",
|
|
description: "Promote or demote any user to Factorio admin.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.set_banned",
|
|
title: "Set user ban status",
|
|
description: "Ban or unban any user.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.set_whitelisted",
|
|
title: "Set user whitelist status",
|
|
description: "Add or remove any user to/from the whitelist.",
|
|
});
|
|
definePermission({
|
|
name: "core.user.delete",
|
|
title: "Delete user",
|
|
description: "Delete users and all data stored for them.",
|
|
});
|
|
|
|
definePermission({
|
|
name: "core.debug.dump_ws",
|
|
title: "Dump WebSocket",
|
|
description: "Dump all WebSocket communicatation from the master.",
|
|
});
|
|
|
|
module.exports = {
|
|
Permission,
|
|
Role,
|
|
User,
|
|
|
|
permissions,
|
|
definePermission,
|
|
ensureDefaultAdminRole,
|
|
ensureDefaultPlayerRole,
|
|
};
|