Introduces zod-based validation schemas for Minecraft and Mojang API endpoints. Refactors texture route to support hash-based file serving and removes the old static texture route. Updates database schema for player properties and adds an event to clean expired certificates. Improves ValidationError formatting, adjusts skin/cape URL construction, and adds SSRF protection for skin uploads.
119 lines
3.6 KiB
JavaScript
119 lines
3.6 KiB
JavaScript
const DefaultError = require("./DefaultError")
|
|
const YggdrasilError = require("./YggdrasilError")
|
|
const SessionError = require("./SessionError")
|
|
const ServiceError = require("./ServiceError")
|
|
const logger = require("../modules/logger")
|
|
|
|
class ValidationError extends DefaultError {
|
|
constructor(result, config = {}, context = {}) {
|
|
let formattedErrors = []
|
|
if (result && result.error && Array.isArray(result.error.issues)) {
|
|
formattedErrors = result.error.issues.flatMap(issue => {
|
|
if (issue.code === "unrecognized_keys") {
|
|
return issue.keys.map(key => ({
|
|
field: [...issue.path, key].join("."),
|
|
message: "Field not allowed"
|
|
}))
|
|
}
|
|
|
|
if (issue.code === "invalid_union") {
|
|
return {
|
|
field: issue.path.join("."),
|
|
message: "Invalid input format (union mismatch)"
|
|
}
|
|
}
|
|
|
|
return {
|
|
field: issue.path.join("."),
|
|
message: issue.message
|
|
}
|
|
})
|
|
}
|
|
else if (result instanceof Error) {
|
|
formattedErrors = [{
|
|
field: "global",
|
|
message: result.message
|
|
}]
|
|
}
|
|
else if (typeof result === "string") {
|
|
formattedErrors = [{
|
|
field: "global",
|
|
message: result
|
|
}]
|
|
}
|
|
else {
|
|
formattedErrors = [{
|
|
field: "unknown",
|
|
message: "Unknown validation error"
|
|
}]
|
|
}
|
|
|
|
const message = config.message || "Validation failed"
|
|
const statusCode = config.code || 400
|
|
|
|
super(statusCode, message, { errors: formattedErrors })
|
|
|
|
this.config = config
|
|
this.formattedErrors = formattedErrors
|
|
this.context = context
|
|
|
|
this.logError()
|
|
}
|
|
|
|
logError() {
|
|
const { method, path, ip } = this.context
|
|
if (method && path) {
|
|
logger.warn(
|
|
`Validation failed for ${method} ${path} (${this.config.errorFormat || "Standard"}) ` +
|
|
`<IP:${ip || "Unknown"}>`,
|
|
["WEB", "yellow"]
|
|
)
|
|
}
|
|
}
|
|
|
|
serialize() {
|
|
if (this.config.errorFormat === "YggdrasilError") {
|
|
const err = new YggdrasilError(
|
|
this.code,
|
|
this.config.errorName || "IllegalArgumentException",
|
|
this.message,
|
|
JSON.stringify(this.formattedErrors)
|
|
)
|
|
return err.serialize()
|
|
}
|
|
|
|
if (this.config.errorFormat === "SessionError") {
|
|
const err = new SessionError(
|
|
this.code,
|
|
this.config.errorName || "Forbidden",
|
|
this.message,
|
|
this.context.path || ""
|
|
)
|
|
return err.serialize()
|
|
}
|
|
|
|
if (this.config.errorFormat === "ServiceError") {
|
|
const err = new ServiceError(
|
|
this.code,
|
|
this.context.path || "",
|
|
this.config.errorName || "ValidationException",
|
|
this.message,
|
|
this.formattedErrors
|
|
)
|
|
return err.serialize()
|
|
}
|
|
const response = {
|
|
code: this.code,
|
|
message: this.message,
|
|
errors: this.formattedErrors
|
|
}
|
|
|
|
if (this.cause) {
|
|
response.cause = this.cause
|
|
}
|
|
|
|
return response
|
|
}
|
|
}
|
|
|
|
module.exports = ValidationError |