Add environment example, update .gitignore, and switch license to AGPL v3. Introduce error handling classes, ESLint config, and main modules for database, logging, certificate management, and utility functions. Add authentication routes, schemas, and service layer for a modular REST API. Update README and set up repository structure for further development.
274 lines
10 KiB
JavaScript
274 lines
10 KiB
JavaScript
const path = require("node:path")
|
|
const Logger = require("../modules/logger")
|
|
const logger = Logger.createLogger(path.join(__dirname, ".."))
|
|
const bcrypt = require("bcryptjs")
|
|
const database = require("../modules/database")
|
|
const { DefaultError } = require("../errors/errors")
|
|
const usernameRegex = /^[a-zA-Z0-9_]{3,16}$/
|
|
|
|
async function getUser(identifier, requirePassword = false) {
|
|
try {
|
|
const sql = `SELECT * FROM players WHERE uuid = ? OR email = ? OR username = ?`
|
|
const rows = await database.query(sql, [identifier, identifier, identifier])
|
|
const user = rows[0]
|
|
if (!user) {
|
|
throw new DefaultError(404, "User not found")
|
|
}
|
|
|
|
delete user.email
|
|
if (!requirePassword) {
|
|
delete user.password
|
|
}
|
|
return { code: 200, user }
|
|
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Internal Server Error", "Please contact an administrator.")
|
|
}
|
|
}
|
|
|
|
async function register(email, username, password) {
|
|
try {
|
|
const availability = await checkUsernameAvailability(username)
|
|
if (availability.allowed) {
|
|
const sql = `INSERT INTO players (email, username, password, uuid) VALUES (?, ?, ?, ?)`
|
|
const uuid = crypto.randomUUID()
|
|
const hashedPassword = await bcrypt.hash(password, 10)
|
|
const result = await database.query(sql, [email, username, hashedPassword, uuid])
|
|
if (result.affectedRows > 0) {
|
|
return { code: 200, email, username, uuid }
|
|
} else {
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
} else {
|
|
throw new DefaultError(415, "Illegal Server Character", availability.message || "INVALID_USERNAME_FORMAT")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
console.log(error)
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function checkUsernameAvailability(username) {
|
|
if (!usernameRegex.test(username)) {
|
|
return { code: 200, allowed: false, message: "Invalid format (3-16 alphanumeric chars)." }
|
|
}
|
|
|
|
const blocklist = await getUsernamesRules()
|
|
const normalizedUsername = username.toLowerCase()
|
|
|
|
for (const entry of blocklist) {
|
|
if (entry.type === "literal") {
|
|
if (normalizedUsername === entry.value) {
|
|
return { code: 200, allowed: false, message: "This username is reserved." }
|
|
}
|
|
}
|
|
else if (entry.type === "regex") {
|
|
if (entry.pattern.test(username)) {
|
|
return { code: 200, allowed: false, message: "This username contains forbidden patterns." }
|
|
}
|
|
}
|
|
}
|
|
|
|
return { code: 200, allowed: true }
|
|
}
|
|
|
|
async function addPropertyToPlayer(key, value, uuid) {
|
|
try {
|
|
const sql = `INSERT INTO playersProperties (name, value, uuid) VALUES (?, ?, ?)`
|
|
const result = await database.query(sql, [key, value, uuid])
|
|
|
|
if (result.affectedRows > 0) {
|
|
return { code: 200, key, value, uuid }
|
|
} else {
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
} catch (error) {
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function deletePropertyToPlayer(key, uuid) {
|
|
try {
|
|
const sql = `DELETE FROM playersProperties WHERE name = ? AND uuid = ?`
|
|
const result = await database.query(sql, [key, uuid])
|
|
|
|
if (result.affectedRows > 0) {
|
|
return { code: 200, key, uuid }
|
|
} else {
|
|
throw new DefaultError(500, "Property not found for this user/key combination.")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function insertClientSession(accessToken, clientToken, uuid) {
|
|
try {
|
|
const sql = `INSERT INTO clientSessions (accessToken, clientToken, uuid) VALUES (?, ?, ?)`
|
|
const result = await database.query(sql, [accessToken, clientToken, uuid])
|
|
|
|
if (result.affectedRows > 0) {
|
|
return { code: 204, accessToken, clientToken }
|
|
} else {
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
} catch (error) {
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
}
|
|
}
|
|
|
|
async function getPlayerProperties(uuid) {
|
|
try {
|
|
const sql = `SELECT * FROM playersProperties WHERE uuid = ?`
|
|
const properties = await database.query(sql, [uuid])
|
|
|
|
if (properties.length === 0) {
|
|
throw new DefaultError(404, "Properties not found for this user.", "InternalServerError")
|
|
}
|
|
return { code: 200, properties: properties.map(property => { return { name: property.name, value: property.value } }) }
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function getClientSession(accessToken, clientToken) {
|
|
try {
|
|
const sql = `SELECT * FROM clientSessions WHERE accessToken = ? AND clientToken = ?`
|
|
const rows = await database.query(sql, [accessToken, clientToken])
|
|
|
|
const session = rows[0]
|
|
if (session) {
|
|
return {
|
|
code: 200,
|
|
session: session
|
|
}
|
|
} else {
|
|
throw new DefaultError(404, "Client session not found")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function validateClientSession(accessToken, clientToken) {
|
|
try {
|
|
const sql = `SELECT * FROM clientSessions WHERE accessToken = ? AND clientToken = ?`
|
|
const rows = await database.query(sql, [accessToken, clientToken])
|
|
|
|
const session = rows[0]
|
|
if (session) {
|
|
return {
|
|
code: 200,
|
|
message: "Client session valid."
|
|
}
|
|
} else {
|
|
throw new DefaultError(404, "Client session not found for this accessToken/clientToken combination.")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function validateClientSessionWithoutClientToken(accessToken) {
|
|
try {
|
|
const sql = `SELECT * FROM clientSessions WHERE accessToken = ?`
|
|
const rows = await database.query(sql, [accessToken])
|
|
|
|
const session = rows[0]
|
|
if (session) {
|
|
return {
|
|
code: 200,
|
|
message: "Client session valid."
|
|
}
|
|
} else {
|
|
throw new DefaultError(404, "Client session not found for this accessToken.")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function invalidateClientSession(accessToken, clientToken) {
|
|
try {
|
|
const sql = `DELETE FROM clientSessions WHERE accessToken = ? AND clientToken = ?`
|
|
const result = await database.query(sql, [accessToken, clientToken])
|
|
|
|
if (result.affectedRows > 0) {
|
|
return {
|
|
code: 200,
|
|
message: "Client session successfully invalidated."
|
|
}
|
|
} else {
|
|
throw new DefaultError(404, "Client session not found for this accessToken/clientToken combination.")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function revokeAccessTokens(uuid) {
|
|
try {
|
|
const sql = `DELETE FROM clientSessions WHERE uuid = ?`
|
|
const result = await database.query(sql, [uuid])
|
|
|
|
if (result.affectedRows > 0) {
|
|
return {
|
|
code: 200,
|
|
message: "Access tokens successfully revoked."
|
|
}
|
|
} else {
|
|
throw new DefaultError(404, "No access token found for this user.")
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof DefaultError) throw error
|
|
logger.log("Internal Server Error".bold + " : " + error.toString(), ["MariaDB", "yellow"])
|
|
throw new DefaultError(500, "Please contact an administrator.", "InternalServerError")
|
|
}
|
|
}
|
|
|
|
async function getUsernamesRules() {
|
|
try {
|
|
const rows = await database.query("SELECT rule, type FROM usernameRules")
|
|
return rows.map(row => {
|
|
if (row.type === 1) {
|
|
return { type: "regex", pattern: new RegExp(row.rule, "i") }
|
|
} else {
|
|
return { type: "literal", value: row.rule.toLowerCase() }
|
|
}
|
|
})
|
|
} catch (err) {
|
|
throw err
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getUser,
|
|
register,
|
|
getClientSession,
|
|
revokeAccessTokens,
|
|
insertClientSession,
|
|
addPropertyToPlayer,
|
|
getPlayerProperties,
|
|
validateClientSession,
|
|
deletePropertyToPlayer,
|
|
invalidateClientSession,
|
|
validateClientSessionWithoutClientToken
|
|
} |