mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 15:24:52 +09:00
A permission function runs during ingress permission checking, before the request body is validated. UserBulkImportRequest.permission read the raw, unvalidated message.data and threw a bare Error for an unknown importType. That error is not a PermissionError, so _processMessage rethrew it to the connector "message" listener, which emitted "error" on the connector. Controller-side connectors have no "error" listener, so Node rethrew and handleUnhandledErrors exited the process. Repro: a control client with client-side validateSent disabled sends UserBulkImportRequest with importType "foo"; the client gets a normal RequestError response and the controller then logs a fatal uncaught exception and exits. Fix both layers: _processMessage now logs and drops an unexpected error from the permission check (control connections only, where a response was already sent) instead of rethrowing, and UserBulkImportRequest throws a PermissionError for an unknown importType so it is denied cleanly. A genuine PermissionError still denies silently as before. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
340 lines
9.0 KiB
TypeScript
340 lines
9.0 KiB
TypeScript
import { Type, Static } from "@sinclair/typebox";
|
|
import UserDetails, { IUser } from "./UserDetails.js";
|
|
import { StringEnum, jsonArray, plainJson } from "./composites.js";
|
|
import { MessageRequest } from "./messages_core.js";
|
|
import { PermissionError } from "../errors.js";
|
|
|
|
export class UserGetRequest {
|
|
declare ["constructor"]: typeof UserGetRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.get" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name);
|
|
}
|
|
|
|
static Response = UserDetails;
|
|
}
|
|
|
|
export class UserListRequest {
|
|
declare ["constructor"]: typeof UserListRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.list" as const;
|
|
static Response = jsonArray(UserDetails);
|
|
}
|
|
|
|
export class UserCreateRequest {
|
|
declare ["constructor"]: typeof UserCreateRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.create" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name);
|
|
}
|
|
}
|
|
|
|
export class UserRevokeTokenRequest {
|
|
declare ["constructor"]: typeof UserRevokeTokenRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.revoke_token" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name);
|
|
}
|
|
}
|
|
|
|
export class UserUpdateRolesRequest {
|
|
declare ["constructor"]: typeof UserUpdateRolesRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.update_roles" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
public roles: number[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
"roles": Type.Array(Type.Integer()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name, json.roles);
|
|
}
|
|
}
|
|
|
|
export class UserSetAdminRequest {
|
|
declare ["constructor"]: typeof UserSetAdminRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.set_admin" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
public create: boolean,
|
|
public admin: boolean,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
"create": Type.Boolean(),
|
|
"admin": Type.Boolean(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name, json.create, json.admin);
|
|
}
|
|
}
|
|
|
|
export class UserSetWhitelistedRequest {
|
|
declare ["constructor"]: typeof UserSetWhitelistedRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.set_whitelisted" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
public create: boolean,
|
|
public whitelisted: boolean,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
"create": Type.Boolean(),
|
|
"whitelisted": Type.Boolean(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name, json.create, json.whitelisted);
|
|
}
|
|
}
|
|
|
|
export class UserSetBannedRequest {
|
|
declare ["constructor"]: typeof UserSetBannedRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.set_banned" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
public create: boolean,
|
|
public banned: boolean,
|
|
public reason: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
"create": Type.Boolean(),
|
|
"banned": Type.Boolean(),
|
|
"reason": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name, json.create, json.banned, json.reason);
|
|
}
|
|
}
|
|
|
|
export class UserDeleteRequest {
|
|
declare ["constructor"]: typeof UserDeleteRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.delete" as const;
|
|
|
|
constructor(
|
|
public name: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"name": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.name);
|
|
}
|
|
}
|
|
|
|
export class UserUpdatesEvent {
|
|
declare ["constructor"]: typeof UserUpdatesEvent;
|
|
static type = "event" as const;
|
|
static src = "controller" as const;
|
|
static dst = "control" as const;
|
|
static permission = "core.user.subscribe" as const;
|
|
|
|
constructor(
|
|
public updates: UserDetails[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"updates": Type.Array(UserDetails.jsonSchema),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.updates.map(update => UserDetails.fromJSON(update)));
|
|
}
|
|
}
|
|
|
|
export class ClusterioUserExport {
|
|
public export_version = "2.0.0-alpha.20"; // Only increment when jsonSchema changes
|
|
|
|
static clusterioUserSchema = Type.Object({
|
|
"username": Type.String(),
|
|
"is_admin": Type.Optional(Type.Boolean()),
|
|
"is_whitelisted": Type.Optional(Type.Boolean()),
|
|
"is_banned": Type.Optional(Type.Boolean()),
|
|
"ban_reason": Type.Optional(Type.String()),
|
|
});
|
|
|
|
static factorioUserSchema = Type.Union([
|
|
Type.String(),
|
|
Type.Object({
|
|
"username": Type.String(),
|
|
"reason": Type.String(),
|
|
}),
|
|
]);
|
|
|
|
static jsonSchema = Type.Object({
|
|
"export_version": Type.String(),
|
|
"users": Type.Array(this.clusterioUserSchema),
|
|
});
|
|
|
|
constructor(
|
|
public users: Static<typeof ClusterioUserExport.jsonSchema.properties.users>,
|
|
) { }
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
const obj = new this(json.users);
|
|
obj.export_version = json.export_version;
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
export class UserBulkImportRequest {
|
|
declare ["constructor"]: typeof UserBulkImportRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission(user: IUser, message: MessageRequest) {
|
|
if (typeof message.data === "object" && message.data !== null) {
|
|
const data = message.data as Static<typeof UserBulkImportRequest.jsonSchema>;
|
|
// Check if this is a restore or import request
|
|
if (data.restore) {
|
|
user.checkPermission("core.user.bulk_restore");
|
|
} else {
|
|
user.checkPermission("core.user.bulk_import");
|
|
}
|
|
// Check if they have the permission for that type
|
|
switch (data.importType) {
|
|
case "users":
|
|
user.checkPermission("core.user.set_admin");
|
|
user.checkPermission("core.user.set_banned");
|
|
user.checkPermission("core.user.set_whitelisted");
|
|
break;
|
|
case "admins":
|
|
user.checkPermission("core.user.set_admin");
|
|
break;
|
|
case "bans":
|
|
user.checkPermission("core.user.set_banned");
|
|
break;
|
|
case "whitelist":
|
|
user.checkPermission("core.user.set_whitelisted");
|
|
break;
|
|
default:
|
|
// Unvalidated input during permission check: deny, don't throw a bare Error
|
|
// @ts-expect-error Unreachable
|
|
throw new PermissionError(`Unknown import / restore type: ${data.importType}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Response = plainJson(Type.Union([
|
|
Type.Array(ClusterioUserExport.factorioUserSchema),
|
|
ClusterioUserExport.jsonSchema,
|
|
]));
|
|
|
|
constructor(
|
|
public importType: "users" | "bans" | "admins" | "whitelist",
|
|
public users: Static<typeof ClusterioUserExport.clusterioUserSchema>[]
|
|
| Static<typeof ClusterioUserExport.factorioUserSchema>[],
|
|
public restore?: boolean
|
|
) { }
|
|
|
|
static jsonSchema = Type.Union([
|
|
Type.Object({
|
|
"importType": StringEnum(["bans", "admins", "whitelist"]),
|
|
"users": Type.Array(ClusterioUserExport.factorioUserSchema),
|
|
"restore": Type.Optional(Type.Boolean()),
|
|
}),
|
|
Type.Object({
|
|
"importType": Type.Literal("users"),
|
|
"users": Type.Array(ClusterioUserExport.clusterioUserSchema),
|
|
"restore": Type.Optional(Type.Boolean()),
|
|
}),
|
|
]);
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.importType, json.users, json.restore);
|
|
}
|
|
}
|
|
|
|
export class UserBulkExportRequest {
|
|
declare ["constructor"]: typeof UserBulkExportRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.user.bulk_export" as const;
|
|
static Response = plainJson(Type.Union([
|
|
Type.Array(ClusterioUserExport.factorioUserSchema),
|
|
ClusterioUserExport.jsonSchema,
|
|
]));
|
|
|
|
constructor(
|
|
public exportType: "users" | "bans" | "admins" | "whitelist",
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"exportType": StringEnum(["users", "bans", "admins", "whitelist"]),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.exportType);
|
|
}
|
|
}
|