first commit
This commit is contained in:
parent
651afd6e36
commit
1b09267e47
|
@ -130,3 +130,7 @@ dist
|
|||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
.env
|
||||
temp
|
||||
data
|
||||
inviteTokens.txt
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "linux-gcc-x64",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "${default}",
|
||||
"cppStandard": "${default}",
|
||||
"intelliSenseMode": "linux-gcc-x64",
|
||||
"compilerArgs": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C/C++ Runner: Debug Session",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": false,
|
||||
"cwd": "/home/leca/projects/js/minecraft-launcher-registration/public/css",
|
||||
"program": "/home/leca/projects/js/minecraft-launcher-registration/public/css/build/Debug/outDebug",
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"C_Cpp_Runner.cCompilerPath": "gcc",
|
||||
"C_Cpp_Runner.cppCompilerPath": "g++",
|
||||
"C_Cpp_Runner.debuggerPath": "gdb",
|
||||
"C_Cpp_Runner.cStandard": "",
|
||||
"C_Cpp_Runner.cppStandard": "",
|
||||
"C_Cpp_Runner.msvcBatchPath": "C:/Program Files/Microsoft Visual Studio/VR_NR/Community/VC/Auxiliary/Build/vcvarsall.bat",
|
||||
"C_Cpp_Runner.useMsvc": false,
|
||||
"C_Cpp_Runner.warnings": [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Wpedantic",
|
||||
"-Wshadow",
|
||||
"-Wformat=2",
|
||||
"-Wcast-align",
|
||||
"-Wconversion",
|
||||
"-Wsign-conversion",
|
||||
"-Wnull-dereference"
|
||||
],
|
||||
"C_Cpp_Runner.msvcWarnings": [
|
||||
"/W4",
|
||||
"/permissive-",
|
||||
"/w14242",
|
||||
"/w14287",
|
||||
"/w14296",
|
||||
"/w14311",
|
||||
"/w14826",
|
||||
"/w44062",
|
||||
"/w44242",
|
||||
"/w14905",
|
||||
"/w14906",
|
||||
"/w14263",
|
||||
"/w44265",
|
||||
"/w14928"
|
||||
],
|
||||
"C_Cpp_Runner.enableWarnings": true,
|
||||
"C_Cpp_Runner.warningsAsError": false,
|
||||
"C_Cpp_Runner.compilerArgs": [],
|
||||
"C_Cpp_Runner.linkerArgs": [],
|
||||
"C_Cpp_Runner.includePaths": [],
|
||||
"C_Cpp_Runner.includeSearch": [
|
||||
"*",
|
||||
"**/*"
|
||||
],
|
||||
"C_Cpp_Runner.excludeSearch": [
|
||||
"**/build",
|
||||
"**/build/**",
|
||||
"**/.*",
|
||||
"**/.*/**",
|
||||
"**/.vscode",
|
||||
"**/.vscode/**"
|
||||
],
|
||||
"C_Cpp_Runner.useAddressSanitizer": false,
|
||||
"C_Cpp_Runner.useUndefinedSanitizer": false,
|
||||
"C_Cpp_Runner.useLeakSanitizer": false,
|
||||
"C_Cpp_Runner.showCompilationTime": false,
|
||||
"C_Cpp_Runner.useLinkTimeOptimization": false,
|
||||
"C_Cpp_Runner.msvcSecureNoWarnings": false
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
FROM node:22-bullseye
|
||||
|
||||
WORKDIR /opt/mcserver
|
||||
|
||||
COPY . .
|
||||
RUN npm i
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["node", "./src/index.js"]
|
|
@ -0,0 +1,65 @@
|
|||
-- This schema was adopted from the gravit launcher's wiki.
|
||||
-- Create the uuid-ossp extension if it doesn't exist
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Create the hwids table if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS hwids (
|
||||
id serial8 NOT NULL PRIMARY KEY,
|
||||
publickey bytea NULL,
|
||||
hwdiskid varchar NULL,
|
||||
baseboardserialnumber varchar NULL,
|
||||
graphiccard varchar NULL,
|
||||
displayid bytea NULL,
|
||||
bitness int NULL,
|
||||
totalmemory bigint NULL,
|
||||
logicalprocessors int NULL,
|
||||
physicalprocessors int NULL,
|
||||
processormaxfreq bigint NULL,
|
||||
battery boolean NULL,
|
||||
banned boolean NULL
|
||||
);
|
||||
|
||||
-- Create the users table if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
uuid CHAR(36) UNIQUE DEFAULT NULL,
|
||||
accessToken CHAR(32) DEFAULT NULL,
|
||||
serverID VARCHAR(41) DEFAULT NULL,
|
||||
hwidid BIGINT REFERENCES hwids(id) DEFAULT NULL,
|
||||
username VARCHAR(32) UNIQUE DEFAULT NULL,
|
||||
password CHAR(60) UNIQUE DEFAULT NULL,
|
||||
can_have_cloak BOOLEAN DEFAULT false
|
||||
);
|
||||
|
||||
-- Create the users_uuid_trigger_func function if it doesn't exist
|
||||
CREATE OR REPLACE FUNCTION public.users_uuid_trigger_func()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$function$
|
||||
BEGIN
|
||||
IF (new.uuid IS NULL) THEN
|
||||
new.uuid = (SELECT uuid_generate_v4());
|
||||
END IF;
|
||||
return new;
|
||||
END;
|
||||
$function$ LANGUAGE plpgsql;
|
||||
|
||||
-- Create the users_uuid_trigger trigger if it doesn't exist
|
||||
CREATE OR REPLACE TRIGGER users_uuid_trigger
|
||||
BEFORE INSERT ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.users_uuid_trigger_func();
|
||||
|
||||
-- Update the users table to generate uuids for existing rows if necessary
|
||||
UPDATE users SET uuid=(SELECT uuid_generate_v4()) WHERE uuid IS NULL;
|
||||
|
||||
|
||||
|
||||
-- Add the primary key constraint to the hwids table if it doesn't exist
|
||||
--ALTER TABLE public.hwids ADD CONSTRAINT hwids_pk PRIMARY KEY (id);
|
||||
|
||||
|
||||
-- Create the unique index on the publickey column of the hwids table if it doesn't exist
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS hwids_publickey_idx ON hwids (publickey);
|
||||
|
||||
-- Add the foreign key constraint to the users table if it doesn't exist
|
||||
-- ALTER TABLE public.users ADD CONSTRAINT IF NOT EXISTS users_hwids_fk FOREIGN KEY (hwidid) REFERENCES public.hwids(id);
|
|
@ -0,0 +1,26 @@
|
|||
services:
|
||||
mcserver:
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 8080:3000
|
||||
depends_on:
|
||||
- database
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./data/skins:/opt/skins
|
||||
- ./data/cloaks:/opt/cloaks
|
||||
|
||||
database:
|
||||
image: 'postgres:15'
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- ./data/db:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: mcserver
|
||||
POSTGRES_PASSWORD: GENERATE_A_STRONG_PASSWORD_HERE
|
||||
POSTGRES_DB: mcserver
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "mcserver"]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "minecraft-launcher-registration",
|
||||
"version": "1.0.0",
|
||||
"description": "Frontend and backend for my minecraft server's registration",
|
||||
"keywords": [
|
||||
"minecraft",
|
||||
"launcher",
|
||||
"registration"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.foxarmy.org/leca/minecraft-launcher-registration"
|
||||
},
|
||||
"license": "WTFPL",
|
||||
"author": "leca",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"jimp": "^1.6.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pg": "^8.13.1",
|
||||
"pug": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.8);
|
||||
margin: auto auto;
|
||||
padding: 40px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.box h2 {
|
||||
margin: 0 0 30px 0;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box .inputBox label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.box .inputBox input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid #fff;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 30px;
|
||||
outline: none;
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.box input[type="submit"], .box button[type="submit"], a.button {
|
||||
font-family: sans-serif;
|
||||
background: #03a9f4;
|
||||
font-size: 11px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
padding: 10px 20px;
|
||||
letter-spacing: 2px;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
margin: 2px 10px 2px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.box input[type="submit"]:hover, .box button[type="submit"]:hover, a.button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#tsparticles {
|
||||
position: fixed;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
tsParticles.loadJSON("tsparticles", "json/particles.json");
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"fpsLimit": 60,
|
||||
"particles": {
|
||||
"number": {
|
||||
"value": 50
|
||||
},
|
||||
"shape": {
|
||||
"type": "circle"
|
||||
},
|
||||
"opacity": {
|
||||
"value": 0.5
|
||||
},
|
||||
"size": {
|
||||
"value": 400,
|
||||
"random": {
|
||||
"enable": true,
|
||||
"minimumValue": 200
|
||||
}
|
||||
},
|
||||
"move": {
|
||||
"enable": true,
|
||||
"speed": 10,
|
||||
"direction": "top",
|
||||
"outMode": "destroy"
|
||||
}
|
||||
},
|
||||
"interactivity": {
|
||||
"detectsOn": "canvas",
|
||||
"events": {
|
||||
"resize": true
|
||||
}
|
||||
},
|
||||
"detectRetina": true,
|
||||
"background": {
|
||||
"image": "",
|
||||
"position": "50% 50%",
|
||||
"repeat": "no-repeat",
|
||||
"size": "cover"
|
||||
},
|
||||
"themes": [
|
||||
{
|
||||
"name": "light",
|
||||
"default": {
|
||||
"value": true,
|
||||
"mode": "light"
|
||||
},
|
||||
"options": {
|
||||
"background": {
|
||||
"color": "#fff"
|
||||
},
|
||||
"particles": {
|
||||
"color": {
|
||||
"value": [
|
||||
"#5bc0eb",
|
||||
"#fde74c",
|
||||
"#9bc53d",
|
||||
"#e55934",
|
||||
"#fa7921"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dark",
|
||||
"default": {
|
||||
"value": true,
|
||||
"mode": "dark"
|
||||
},
|
||||
"options": {
|
||||
"background": {
|
||||
"color": "#000"
|
||||
},
|
||||
"particles": {
|
||||
"color": {
|
||||
"value": [
|
||||
"#004f74",
|
||||
"#5f5800",
|
||||
"#245100",
|
||||
"#7d0000",
|
||||
"#810c00"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"emitters": {
|
||||
"direction": "top",
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 120
|
||||
},
|
||||
"rate": {
|
||||
"delay": 0.2,
|
||||
"quantity": 2
|
||||
},
|
||||
"size": {
|
||||
"width": 100,
|
||||
"height": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
SECRET=GENERATE_A_STRONG_SECRET_HERE
|
||||
DBUSER=mcserver
|
||||
DBHOST=localhost
|
||||
DBNAME=mcserver
|
||||
DBPORT=5432
|
||||
DBPASS=GENERATE_A_STRONG_PASSWORD_HERE
|
||||
PORT=3000
|
||||
REQUIRE_TOKEN=false
|
||||
DELETE_TOKEN_ON_USE=true
|
|
@ -0,0 +1,95 @@
|
|||
import bcrypt from 'bcrypt';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { Jimp } from 'jimp';
|
||||
import fs from 'fs';
|
||||
|
||||
import UserService from "../services/user.js";
|
||||
import utils from '../utils.js';
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
class UserController {
|
||||
async register(req, res) {
|
||||
const {username, password, passwordConfirm} = req.body;
|
||||
|
||||
if (password != passwordConfirm) return res.status(400).send("Passwords do not match");
|
||||
|
||||
let hashedPassword = await bcrypt.hash(password, 8);
|
||||
|
||||
await UserService.register(username, hashedPassword);
|
||||
|
||||
if (process.env.REQUIRE_TOKEN == "true" && process.env.DELETE_TOKEN_ON_USE == "true") {
|
||||
utils.removeFromFile('./inviteTokens.txt', req.body.inviteToken);
|
||||
}
|
||||
|
||||
req.session.jwt = jwt.sign({ username }, process.env.SECRET, {expiresIn: "1y"});
|
||||
return res.redirect("/index");
|
||||
}
|
||||
|
||||
async login(req, res) {
|
||||
const {username, password} = req.body;
|
||||
|
||||
const storedPassword = await UserService.getPassword(username);
|
||||
|
||||
if (!bcrypt.compareSync(password, storedPassword)) {
|
||||
return res.status(403).send("Password is not correct");
|
||||
}
|
||||
req.session.jwt = jwt.sign({ username }, process.env.SECRET, {expiresIn: "1y"});
|
||||
return res.redirect("/index");
|
||||
}
|
||||
|
||||
async logout(req, res) {
|
||||
req.session.destroy();
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
async uploadSkin(req, res) {
|
||||
const token = req.session.jwt;
|
||||
const decoded = jwt.decode(token);
|
||||
const tempPath = req.file.path;
|
||||
const targetPath = `/opt/skins/${decoded.username}.png`;
|
||||
|
||||
if (path.extname(req.file.originalname).toLowerCase() !== ".png") {
|
||||
return res.status(400).send("Only .png files are allowed!");
|
||||
}
|
||||
|
||||
|
||||
const image = await Jimp.read(tempPath);
|
||||
if (image.bitmap.width != 64 || image.bitmap.height != 64) {
|
||||
fs.unlinkSync(targetPath);
|
||||
return res.status(400).send('This does not look like a minecraft skin.');
|
||||
}
|
||||
|
||||
fs.renameSync(tempPath, targetPath, err => {
|
||||
if (err) return res.status(500).send("Ooops! Something went wrong! Please, report to the developer.");
|
||||
});
|
||||
return res.status(200).send("Skin uploaded!");
|
||||
}
|
||||
|
||||
async uploadCape(req, res) {
|
||||
const token = req.session.jwt;
|
||||
const decoded = jwt.decode(token);
|
||||
const tempPath = req.file.path;
|
||||
const targetPath = `/opt/cloaks/${decoded.username}.png`;
|
||||
|
||||
if (path.extname(req.file.originalname).toLowerCase() !== ".png") {
|
||||
return res.status(400).send("Only .png files are allowed!");
|
||||
}
|
||||
|
||||
|
||||
const image = await Jimp.read(tempPath);
|
||||
if ((image.bitmap.width != 64 || image.bitmap.height != 32) && (image.bitmap.width != 128 || image.bitmap.height != 64)) {
|
||||
fs.unlinkSync(tempPath);
|
||||
return res.status(400).send('This does not look like a minecraft cape.');
|
||||
}
|
||||
|
||||
fs.renameSync(tempPath, targetPath, err => {
|
||||
if (err) return res.status(500).send("Ooops! Something went wrong! Please, report to the developer.");
|
||||
});
|
||||
|
||||
return res.status(200).send("Cape uploaded!");
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserController();
|
|
@ -0,0 +1,22 @@
|
|||
import pg from 'pg';
|
||||
import fs from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
|
||||
const { Pool } = pg;
|
||||
|
||||
console.log("Connecting to PostgreSQL database");
|
||||
|
||||
const pool = new Pool({
|
||||
user: process.env.DBUSER,
|
||||
host: process.env.DBHOST,
|
||||
database: process.env.DBNAME,
|
||||
password: process.env.DBPASS,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
|
||||
pool.query(fs.readFileSync('./db_schema.psql').toString());
|
||||
|
||||
|
||||
export default pool;
|
|
@ -0,0 +1,32 @@
|
|||
import express from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import session from 'express-session';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import path from 'path';
|
||||
|
||||
import ApiRouter from './routers/api.js';
|
||||
import UserRouter from './routers/user.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
|
||||
app.use(session({
|
||||
secret: process.env.SECRET,
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
cookie: { maxAge: 1000 * 60 * 60 * 24 }
|
||||
}));
|
||||
app.use(express.static(path.join('./public')));
|
||||
app.use(express.urlencoded({extended: false}));
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
app.use('/api', ApiRouter);
|
||||
app.use('/', UserRouter);
|
||||
|
||||
app.listen(process.env.PORT, () => {
|
||||
console.log("App has been started!");
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import fs from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
|
||||
const authenticate = async (req, res, next) => {
|
||||
const token = req.session.jwt;
|
||||
if (!token || !jwt.verify(token, process.env.SECRET)) {
|
||||
req.session.destroy();
|
||||
return res.redirect("/login");
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
const validateInviteToken = async (req, res, next) => {
|
||||
if (process.env.REQUIRE_TOKEN != "true") return next();
|
||||
|
||||
const { inviteToken } = req.body;
|
||||
fs.appendFileSync('./inviteTokens.txt', '');
|
||||
const inviteTokens = fs.readFileSync('./inviteTokens.txt').toString().split('\n');
|
||||
let tokenValid = false;
|
||||
|
||||
inviteTokens.forEach((token) => {
|
||||
if (token == inviteToken) tokenValid = true;
|
||||
});
|
||||
|
||||
if (!tokenValid) return res.status(400).send("Token is not valid");
|
||||
next();
|
||||
};
|
||||
|
||||
export default {authenticate, validateInviteToken};
|
|
@ -0,0 +1,22 @@
|
|||
import UserService from '../services/user.js';
|
||||
|
||||
const userDoesNotExist = async (req, res, next) => {
|
||||
|
||||
const { username } = req.body;
|
||||
|
||||
if (await UserService.exists(username)) {
|
||||
return res.status(401).send("Such user exists!");
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
const userExist = async (req, res, next) => {
|
||||
const { username } = req.body;
|
||||
|
||||
if (!(await UserService.exists(username))) {
|
||||
return res.status(401).send("Such user does not exists!");
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export default {userDoesNotExist, userExist};
|
|
@ -0,0 +1,31 @@
|
|||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
|
||||
const requireUsername = async (req, res, next) => {
|
||||
const { username } = req.body;
|
||||
|
||||
if (!username) return res.status(400).send("Username is requires");
|
||||
next();
|
||||
};
|
||||
|
||||
const requirePassword = async (req, res, next) => {
|
||||
const { password } = req.body;
|
||||
|
||||
if (!password) return res.status(400).send("Password is required");
|
||||
next();
|
||||
};
|
||||
|
||||
const requireInviteToken = async (req, res, next) => {
|
||||
const { inviteToken } = req.body;
|
||||
|
||||
if (!inviteToken && process.env.REQUIRE_TOKEN == "true") return res.status(400).send("Invite token is required");
|
||||
next();
|
||||
};
|
||||
|
||||
const requireFile = async (req, res, next) => {
|
||||
if (!req.file) return res.status(400).send("Please, select a file!");
|
||||
next();
|
||||
}
|
||||
|
||||
export default {requireUsername, requirePassword, requireInviteToken, requireFile};
|
|
@ -0,0 +1,18 @@
|
|||
import { Router } from 'express';
|
||||
|
||||
import requiredParameters from '../middlewares/requiredParameters.js';
|
||||
import existance from '../middlewares/existance.js';
|
||||
import auth from '../middlewares/auth.js';
|
||||
|
||||
import utils from '../utils.js';
|
||||
import UserController from '../controllers/user.js';
|
||||
|
||||
const ApiRouter = new Router();
|
||||
|
||||
ApiRouter.post('/register', requiredParameters.requireUsername, requiredParameters.requirePassword, auth.validateInviteToken, existance.userDoesNotExist, UserController.register);
|
||||
ApiRouter.post('/login', requiredParameters.requireUsername, requiredParameters.requirePassword, existance.userExist, UserController.login);
|
||||
ApiRouter.get('/logout', auth.authenticate, UserController.logout);
|
||||
ApiRouter.post('/uploadSkin', auth.authenticate, utils.upload.single('file'), requiredParameters.requireFile, UserController.uploadSkin);
|
||||
ApiRouter.post('/uploadCape', auth.authenticate, utils.upload.single('file'), requiredParameters.requireFile, UserController.uploadCape);
|
||||
|
||||
export default ApiRouter;
|
|
@ -0,0 +1,40 @@
|
|||
import { Router } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
import auth from '../middlewares/auth.js';
|
||||
import UserService from '../services/user.js';
|
||||
|
||||
dotenv.config({path: ".env"});
|
||||
|
||||
const UserRouter = new Router();
|
||||
|
||||
UserRouter.get('/register', async (req, res) => {
|
||||
if (req.session.jwt && jwt.verify(req.session.jwt, process.env.SECRET))
|
||||
return res.redirect("/index");
|
||||
|
||||
return res.render("register.pug", {
|
||||
require_token: process.env.REQUIRE_TOKEN == "true"? true : false
|
||||
});
|
||||
});
|
||||
|
||||
UserRouter.get(['/', '/login'], async (req, res) => {
|
||||
if(req.session.jwt && jwt.verify(req.session.jwt, process.env.SECRET))
|
||||
return res.redirect("/index");
|
||||
|
||||
return res.render("login.pug");
|
||||
});
|
||||
|
||||
UserRouter.get('/index', auth.authenticate, async (req, res) => {
|
||||
if (!req.session.jwt || !jwt.verify(req.session.jwt, process.env.SECRET))
|
||||
return res.redirect("/login");
|
||||
|
||||
const decoded = jwt.decode(req.session.jwt);
|
||||
|
||||
return res.render('index.pug', {
|
||||
username: decoded.username,
|
||||
can_have_cloak: await UserService.canHaveCloak(decoded.username)
|
||||
});
|
||||
})
|
||||
|
||||
export default UserRouter;
|
|
@ -0,0 +1,21 @@
|
|||
import db from '../db.js';
|
||||
|
||||
class UserService {
|
||||
async register(username, password) {
|
||||
await db.query("INSERT INTO users (username, password) VALUES ($1, $2)", [username, password]);
|
||||
}
|
||||
|
||||
async exists(username) {
|
||||
return (await db.query("SELECT * FROM users WHERE username = $1", [username])).rowCount > 0;
|
||||
}
|
||||
|
||||
async getPassword(username) {
|
||||
return (await db.query("SELECT password FROM users WHERE username = $1", [username])).rows[0].password;
|
||||
}
|
||||
|
||||
async canHaveCloak(username) {
|
||||
return (await db.query("SELECT can_have_cloak FROM users WHERE username = $1", [username])).rows[0].can_have_cloak;
|
||||
}
|
||||
};
|
||||
|
||||
export default new UserService();
|
|
@ -0,0 +1,24 @@
|
|||
import multer from "multer";
|
||||
import fs from 'fs';
|
||||
|
||||
const upload = multer({
|
||||
dest: "./temp",
|
||||
fileSize: 3072
|
||||
});
|
||||
|
||||
const removeFromFile = (filename, toRemove) => {
|
||||
let content = fs.readFileSync(filename).toString();
|
||||
let dataArray = content.split('\n');
|
||||
let lastIndex = -1;
|
||||
for (let i = 0; i < dataArray.length; i ++) {
|
||||
if (dataArray[i].includes(toRemove)) {
|
||||
lastIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dataArray.splice(lastIndex, 1);
|
||||
const updatedData = dataArray.join('\n');
|
||||
fs.writeFileSync(filename, updatedData);
|
||||
}
|
||||
|
||||
export default {upload, removeFromFile};
|
|
@ -0,0 +1,16 @@
|
|||
html
|
||||
head
|
||||
title Личный кабинет
|
||||
body
|
||||
h1 Личный кабинет
|
||||
p Имя пользователя: #{username}
|
||||
p Скин:
|
||||
form(method="post" enctype="multipart/form-data" action="/api/uploadskin")
|
||||
input(type="file" name="file")
|
||||
input(type="submit" value="Загрузить")
|
||||
if can_have_cloak
|
||||
p Плащ:
|
||||
form(method="post" enctype="multipart/form-data" action="/api/uploadCape")
|
||||
input(type="file", name="file")
|
||||
input(type="submit", value="Загрузить")
|
||||
a(href="/api/logout") Выйти
|
|
@ -0,0 +1,29 @@
|
|||
html
|
||||
head
|
||||
title Вход
|
||||
meta(charset="utf-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
||||
meta(name="description" content="")
|
||||
link(href="css/particles.css" rel="stylesheet")
|
||||
link(href="css/auth.css" rel="stylesheet")
|
||||
body
|
||||
div(id="tsparticles")
|
||||
main(class="box")
|
||||
h2 Вход
|
||||
|
||||
form(method="post", action="/api/login")
|
||||
div(class="inputBox")
|
||||
label(for="username") Ник
|
||||
input(type="text" name="username" id="username" placeholder="ваш ник на сервере" required=true)
|
||||
|
||||
div(class="inputBox")
|
||||
label(for="password") Пароль
|
||||
input(type="password" name="password" id="password" placeholder="ваш пароль" required=true)
|
||||
|
||||
div
|
||||
button(type="submit" name="" style="float: left;") Войти
|
||||
a(class="button" href="register" style="float: left;") Регистрация
|
||||
|
||||
script(src="https://cdn.jsdelivr.net/npm/tsparticles@1.34.1/tsparticles.min.js" integrity="sha256-D6LXCdCl4meErhc25yXnxIFUtwR96gPo+GtLYv89VZo=" crossorigin="anonymous")
|
||||
script(type="text/javascript" src="js/particles.js")
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
html
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
||||
meta(name="description" content="")
|
||||
link(href="css/particles.css" rel="stylesheet")
|
||||
link(href="css/auth.css" rel="stylesheet")
|
||||
title Регистрация
|
||||
|
||||
body
|
||||
div(id="tsparticles")
|
||||
main(class="box")
|
||||
h2 Регистрация
|
||||
|
||||
form(action="/api/register" method="POST")
|
||||
div(class="inputBox")
|
||||
label(for="username") Ник
|
||||
input(type="text" name="username" id="username" placeholder="ваш ник на сервере" required)
|
||||
div(class="inputBox")
|
||||
label(for="password") Пароль
|
||||
input(type="password" name="password" id="password" placeholder="ваш пароль" required)
|
||||
div(class="inputBox")
|
||||
label(for="passwordConfirm") Повтор пароля
|
||||
input(type="password" name="passwordConfirm" id="passwordConfirm" placeholder="повторите пароль" required)
|
||||
if require_token
|
||||
div(class="inputBox")
|
||||
label(for="inviteToken") Токен приглашения
|
||||
input(type="text" name="inviteToken" id="inviteToken" placeholder="код приглашения" required)
|
||||
button(type="submit" name="" style="float: left;") Зарегистрироваться
|
||||
a(class="button" href="login" style="float: left;") Войти
|
||||
script(src="https://cdn.jsdelivr.net/npm/tsparticles@1.34.1/tsparticles.min.js" integrity="sha256-D6LXCdCl4meErhc25yXnxIFUtwR96gPo+GtLYv89VZo=" crossorigin="anonymous")
|
||||
script(type="text/javascript" src="js/particles.js")
|
Loading…
Reference in New Issue