Compare commits

...

No commits in common. "f3f36938df4095c4885fbddac51c632f23b2e181" and "8ec3e6ff5520750ee5261abc9ff585bebb2cdfad" have entirely different histories.

69 changed files with 7147 additions and 346 deletions

View File

@ -1,6 +0,0 @@
.git
.editorconfig
docker-minecraft.png
LICENSE
Makefile
README.md

View File

@ -1,16 +0,0 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{yaml,yml}]
indent_size = 2
[Makefile]
indent_size = 4
indent_style = tab

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
github: PHLAK
patreon: PHLAK
custom: https://paypal.me/ChrisKankiewicz

View File

@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: docker
directory: "/"
schedule:
interval: monthly
timezone: US/Arizona
open-pull-requests-limit: 10

View File

@ -1,40 +0,0 @@
name: Publish Image
on:
push:
branches: ['master']
tags: ['*']
env:
DOCKER_HUB_USER: phlak
jobs:
build-and-push:
name: Build & Push
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ env.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Extract Metadata
id: extract-metadata
uses: docker/metadata-action@v3
with:
images: ${{ env.DOCKER_HUB_USER }}/minecraft
tags: |
type=raw,value=latest
type=ref,event=tag
- name: Build & Push Image
uses: docker/build-push-action@v2
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.extract-metadata.outputs.tags }}
labels: ${{ steps.extract-metadata.outputs.labels }}

View File

@ -1,15 +0,0 @@
name: Tests
on: [pull_request_target]
jobs:
build-image:
name: Build Image
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Build Image
run: make build

4
.gitignore vendored
View File

@ -1,3 +1 @@
data/
db/
files/server.jar
node_modules/

View File

@ -1,49 +0,0 @@
FROM alpine:3.16.0
LABEL maintainer='Andriy Cherniy <qugalet@m0e.space>'
# Minecraft version
ARG MC_VERSION=bta
ARG MC_JAR_SHA1=8399e1211e95faa421c1507b322dbeae86d604df
# Set default JVM options
ENV _JAVA_OPTIONS '-Xms256M -Xmx4G'
ARG JAVA_VERSION=19
# Create Minecraft directories
RUN mkdir -pv /opt/minecraft /etc/minecraft
# Create non-root user
RUN adduser -DHs /sbin/nologin minecraft
# Add the EULA file
COPY files/eula.txt /etc/minecraft/eula.txt
# Add the ops script
COPY files/ops /usr/local/bin/ops
RUN chmod +x /usr/local/bin/ops
# Install dependencies, fetch Minecraft server jar file and chown files
#ARG JAR_URL=https://launcher.mojang.com/v1/objects/${MC_JAR_SHA1}/server.jar
RUN apk add --update ca-certificates nss tzdata wget \
&& apk add openjdk19-jre \
#&& wget -O /opt/minecraft/minecraft_server.jar ${JAR_URL} \
#&& apk del --purge wget && rm -rf /var/cache/apk/* \
&& rm -rf /var/cache/apk/* \
&& chown -R minecraft:minecraft /etc/minecraft /opt/minecraft
COPY files/server.jar /opt/minecraft/minecraft_server.jar
# Define volumes
VOLUME /etc/minecraft
# Expose port
EXPOSE 25565
# Set running user
USER minecraft
# Set the working dir
WORKDIR /etc/minecraft
# Default run command
CMD ["java", "-jar", "/opt/minecraft/minecraft_server.jar", "nogui"]

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Chris Kankiewicz <Chris@ChrisKankiewicz.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,8 +0,0 @@
IMAGE_NAME="phlak/minecraft"
IMAGE_TAG="$$(grep 'ARG MC_VERSION' Dockerfile | awk -F = '{print $$2}')"
build:
@docker build --force-rm --pull --tag $(IMAGE_NAME):$(IMAGE_TAG) .
purge:
@docker image rm --force $(IMAGE_NAME):$(IMAGE_TAG)

View File

@ -1,123 +0,0 @@
docker-minecraft
================
<p align="center">
<img src="docker-minecraft.png" alt="Docker Minecraft" width="500">
</p>
<p align="center">
<a href="https://github.com/PHLAK/docker-minecraft/discussions"><img src="https://img.shields.io/badge/Join_the-Community-7b16ff.svg?style=for-the-badge" alt="Join the Community"></a>
<a href="https://github.com/users/PHLAK/sponsorship"><img src="https://img.shields.io/badge/Become_a-Sponsor-cc4195.svg?style=for-the-badge" alt="Become a Sponsor"></a>
<a href="https://paypal.me/ChrisKankiewicz"><img src="https://img.shields.io/badge/Make_a-Donation-006bb6.svg?style=for-the-badge" alt="One-time Donation"></a>
<br>
<a href="https://hub.docker.com/repository/docker/phlak/minecraft/tags"><img alt="Docker Image Version" src="https://img.shields.io/docker/v/phlak/minecraft?style=flat-square&sort=semver"></a>
<a href="https://hub.docker.com/repository/docker/phlak/minecraft"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/phlak/minecraft?style=flat-square"></a>
<a href="https://github.com/PHLAK/docker-minecraft/blob/master/LICENSE"><img src="https://img.shields.io/github/license/PHLAK/docker-minecraft?style=flat-square" alt="License"></a>
<a href="https://hub.docker.com/r/phlak/minecraft/builds"><img alt="Docker Cloud Build Status" src="https://img.shields.io/docker/cloud/build/phlak/minecraft"></a>
</p>
<p align="center">
Docker image for <a href="https://minecraft.net/">Minecraft</a> server.
</p>
---
Running the Container
---------------------
First create a named data volume to hold the persistent world and config data:
docker volume create --name minecraft-data
Then run the Minecraft server:
docker run -it -d -p 25565:25565 -v minecraft-data:/etc/minecraft --name minecraft-server phlak/minecraft
#### Optional 'docker run' Arguments
<dl>
<dt><code>-e _JAVA_OPTIONS='-Xms256M -Xmx2048M'</code></dt>
<dd>Set JVM arguments for minimum/maximum memory consumption (default: '-Xms256M -Xmx2048M')</dd>
</dl>
<dl>
<dt><code>-e TZ=America/Phoenix</code></dt>
<dd>Set the timezone for your server. You can find your timezone in this <a href="https://goo.gl/uy1J6q">list of timezones</a>. Use the (case sensitive) value from the <code>TZ</code> column. If left unset, timezone will be UTC.</dd>
</dl>
<dl>
<dt><code>--restart unless-stopped</code></dt>
<dd>Always restart the container regardless of the exit status, but do not start it on daemon startup if the container has been put to a stopped state before. See the Docker <a href="https://goo.gl/Y0dlDH">restart policies</a> for additional details.</dd>
</dl>
**NOTE:** See the [Minecraft Wiki](http://minecraft.gamepedia.com/Server/Requirements) for more info
on memory requirements.
Editing the Server Config
-------------------------
Once you have a running container, you can edit the Minecraft [server config](https://minecraft.gamepedia.com/Server.properties) with:
docker exec -it minecraft-server vi /etc/minecraft/server.properties
After saving changes, restart your container with `docker restart minecraft-server`
Adding OPs
----------
Once you have a running server container you can add OPs by running:
docker exec minecraft-server ops [PLAYER_NAMES]
**NOTE:** Replace `[PLAYER_NAMES]` with the name of one or more players you wish to give OP
privileges separated by a space. If a players name contains spaces wrap it in quotation marks.
Here's an example granting OP to three players with name's `Marty`, `Jennifer` and `Doc Brown`:
docker exec minecraft-server ops Marty Jennifer "Doc Brown"
Running Server Commands
-----------------------
You can run [commands](https://minecraft.gamepedia.com/Commands) on the server
(e.g. `kick`, `ban`, `say`, etc.) by attaching to the running container and
running the commands. Attach to the server by running:
docker attach minecraft-server
Once attached you can run your commands like normal.
say Hello world!
[10:11:56] [Server thread/INFO]: [Server] Hello world!
list
[10:12:08] [Server thread/INFO]: There are 3 of a max 10 players online: Marty, Jennifer, Doc Brown
seed
[10:12:19] [Server thread/INFO]: Seed: [-5234790158571010769]
**NOTE:** In order to detach from the container and leave it running use the
`Ctrl + P` then `Ctrl + Q` key sequence.
Upgrading the Server
--------------------
First pull down the latest image:
docker pull phlak/minecraft
Remove your running server container:
docker rm -f minecraft-server
And run a new one with the same command/arguments as before.
Troubleshooting
---------------
For general help and support join our [GitHub Discussions](https://github.com/PHLAK/docker-minecraft/discussions) or reach out on [Twitter](https://twitter.com/PHLAK).
Please report bugs to the [GitHub Issue Tracker](https://github.com/PHLAK/docker-minecraft/issues).
Copyright
---------
This project is licensed under the [MIT License](https://github.com/PHLAK/docker-minecraft/blob/master/LICENSE).

View File

@ -1,12 +0,0 @@
* Minecraft Docker
Dockerfile and docker-compose for your own server jar
** Usage
Change your JAVA_VERSION in [[./Dockerfile][Dockerfile]] and build:
#+BEGIN_SRC shell
docker compose build
#+END_SRC
Place server.jar to ~files/~ folder, change your [[./docker-compose.yml][Docker Compose]] file and run:
#+BEGIN_SRC shell
docker compose up -d
#+END_SRC

BIN
auth_concept.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

25
backend/.eslintrc.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
env: {
es2021: true,
node: true
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'prettier'],
rules: {
yoda: 'error',
eqeqeq: 'error',
complexity: 'error',
'prefer-const': 'error',
'prefer-template': 'error',
'object-shorthand': 'warn',
'prettier/prettier': 'warn',
'prefer-destructuring': 'warn',
'prefer-arrow-callback': 'error',
'@typescript-eslint/no-namespace': 'off'
}
};

4
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
dist/
.env
README.md

12
backend/.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"arrowParens": "avoid",
"bracketSpacing": true,
"semi": true,
"printWidth": 100,
"trailingComma": "none",
"endOfLine": "lf"
}

23
backend/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM node:18-alpine
WORKDIR /app
COPY ./package.json ./
RUN npm i
COPY ./.env ./
COPY ./prisma/schema.prisma ./
RUN npm run migrate
RUN npm run generate
COPY . .
RUN npm run build
EXPOSE 8080
CMD ["node", "dist"]

View File

@ -0,0 +1,9 @@
version: '2'
services:
backend:
build: .
restart: unless-stopped
ports:
- '9030:8080'
env_file:
- ./.env

48
backend/package.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "rpz_auth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate": "prisma generate",
"migrate": "prisma migrate deploy",
"build": "rm -rf dist/** && tsc",
"dev": "tsc -w",
"start": "node ./dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@tsconfig/node18": "^1.0.1",
"@types/jsonwebtoken": "^8.5.9",
"@types/minio": "^7.0.15",
"@types/node": "^18.11.17",
"@types/passport-local": "^1.0.34",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"eslint": "^8.30.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.8.1",
"typescript": "^4.9.4"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.236.0",
"@fastify/autoload": "^5.6.0",
"@fastify/cookie": "^8.3.0",
"@fastify/passport": "^2.2.0",
"@fastify/secure-session": "^5.3.0",
"@prisma/client": "^4.8.0",
"axios": "^0.27.2",
"discord-api-types": "^0.37.24",
"fastify": "^4.10.2",
"json-schema-to-ts": "^2.6.2",
"jsonwebtoken": "^8.5.1",
"minio": "^7.0.32",
"nanoid": "^3.3.4",
"passport-local": "^1.0.0",
"prisma": "^4.8.0",
"redis": "^4.5.1",
"simple-oauth2": "^4.3.0"
}
}

1694
backend/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"nickname" TEXT NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"userNickname" TEXT NOT NULL,
"ip" TEXT NOT NULL,
"expiredAfter" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_nickname_key" ON "User"("nickname");
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userNickname_fkey" FOREIGN KEY ("userNickname") REFERENCES "User"("nickname") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Session" ADD COLUMN "verified" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "nickname" DROP NOT NULL;

View File

@ -0,0 +1,13 @@
/*
Warnings:
- You are about to drop the column `userNickname` on the `Session` table. All the data in the column will be lost.
- Added the required column `nickname` to the `Session` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Session" DROP CONSTRAINT "Session_userNickname_fkey";
-- AlterTable
ALTER TABLE "Session" DROP COLUMN "userNickname",
ADD COLUMN "nickname" TEXT NOT NULL;

View File

@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Link" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"link" TEXT NOT NULL,
CONSTRAINT "Link_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Link_sessionId_key" ON "Link"("sessionId");
-- AddForeignKey
ALTER TABLE "Link" ADD CONSTRAINT "Link_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "Session"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "hasPass" BOOLEAN NOT NULL DEFAULT false;

View File

@ -0,0 +1,28 @@
-- CreateTable
CREATE TABLE "Season" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"starts" TIMESTAMP(3),
"ends" TIMESTAMP(3),
CONSTRAINT "Season_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isBanned" BOOLEAN NOT NULL DEFAULT false,
"bannedDate" TIMESTAMP(3),
"bannedReason" TEXT,
"seasonId" TEXT,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_seasonId_fkey" FOREIGN KEY ("seasonId") REFERENCES "Season"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `hasPass` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Season" ADD COLUMN "current" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "User" DROP COLUMN "hasPass";

View File

@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[discordId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "discordId" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "User_discordId_key" ON "User"("discordId");

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "tokenVersion" INTEGER NOT NULL DEFAULT 0;

View File

@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `form` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "form" JSONB NOT NULL;

View File

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `form` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "form";

View File

@ -0,0 +1,22 @@
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "guildId" TEXT;
-- CreateTable
CREATE TABLE "Guild" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"discordRole" TEXT NOT NULL,
"leaderId" TEXT NOT NULL,
CONSTRAINT "Guild_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Guild_leaderId_key" ON "Guild"("leaderId");
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Guild" ADD CONSTRAINT "Guild_leaderId_fkey" FOREIGN KEY ("leaderId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,15 @@
/*
Warnings:
- You are about to drop the `Link` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Session` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Link" DROP CONSTRAINT "Link_sessionId_fkey";
-- DropTable
DROP TABLE "Link";
-- DropTable
DROP TABLE "Session";

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "password" TEXT;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@ -0,0 +1,67 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Season {
id String @id
name String
starts DateTime?
ends DateTime?
accounts Account[]
current Boolean @default(true)
}
model Account {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String
creationDate DateTime @default(now())
isBanned Boolean @default(false)
bannedDate DateTime?
bannedReason String?
season Season? @relation(fields: [seasonId], references: [id])
seasonId String?
guild Guild? @relation(fields: [guildId], references: [id])
guildId String?
leaderOf Guild? @relation(name: "guildLeader")
}
model Guild {
id String @id
name String
description String
discordRole String
leader Account @relation(name: "guildLeader", fields: [leaderId], references: [id])
leaderId String @unique
members Account[]
}
model User {
id String @id @default(uuid())
nickname String? @unique
discordId String? @unique
accounts Account[]
tokenVersion Int @default(0)
password String?
}
// model Session {
// id String @id @default(uuid())
// nickname String
// ip String
// verified Boolean @default(false)
// expiredAfter DateTime
// link Link?
// }
// model Link {
// id String @id
// session Session @relation(fields: [sessionId], references: [id])
// sessionId String @unique
// link String
// }

108
backend/src/index.ts Normal file
View File

@ -0,0 +1,108 @@
import fastify from 'fastify';
import fastifyAutoload from '@fastify/autoload';
import { PrismaClient } from '@prisma/client';
// import { Client as MinioClient } from 'minio';
import { S3Client } from '@aws-sdk/client-s3';
import fastifyCookie from '@fastify/cookie';
// import { readFile } from 'fs/promises';
// import { createClient, RedisClientType } from 'redis';
// import fastifySchedule from '@fastify/schedule';
// import fastifyCors from '@fastify/cors';
import path from 'path';
import { env } from 'process';
// import { FromSchema } from 'json-schema-to-ts';
declare module 'fastify' {
interface FastifyInstance {
db: PrismaClient;
storage: S3Client;
// redis: RedisClientType;
}
interface FastifyReply {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendError: (error: any, code?: number, explanation?: string) => void;
}
}
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production';
PORT: number;
JWT_REFRESH_SECRET: string;
JWT_ACCESS_SECRET: string;
AUTH_CLIENT_ID: string;
AUTH_CLIENT_SECRET: string;
REDIS_URL: string;
URL_TOKEN: string;
S3_ENDPOINT: string;
S3_ID: string;
S3_KEY: string;
AUTH_SECRET: string;
AUTH_SALT: string;
}
}
}
fastify({
logger: true,
ajv: { customOptions: { allErrors: true } }
})
.decorate('db', new PrismaClient())
.decorate(
'storage',
new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: 'us-east',
credentials: {
accessKeyId: process.env.S3_ID,
secretAccessKey: process.env.S3_KEY
}
})
)
.register(fastifyCookie)
// .decorate('redis', async () => {
// const client = createClient({
// url: process.env.REDIS_URL
// });
// client.on('error', err => console.log('Redis Client Error', err));
// await client.connect();
// return client;
// })
.addHook('onRoute', route => console.log(route.url))
// .register(fastifyCors, {
// origin: ['http://127.0.0.1:3000'],
// credentials: true
// })
.decorateReply(
'sendError',
function (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any,
code = 500,
explanation = 'Unexpected error occured. Issue will be reported to staff.'
) {
this.log.error(error);
this.code(code).send(new Error(explanation));
}
)
.register(fastifyAutoload, {
dir: path.join(__dirname, 'routes'),
routeParams: true,
autoHooks: true,
options: { prefix: '/api' }
})
// .register(fastifyAutoload, {
// dir: path.join(__dirname, 'link'),
// routeParams: true,
// autoHooks: true,
// options: { prefix: '/a' }
// })
.listen({ port: process.env.PORT || 8080, host: '0.0.0.0' })
// eslint-disable-next-line no-console
.then(() => console.log('Server Started'))
// eslint-disable-next-line no-console
.catch(console.error);

View File

@ -0,0 +1,51 @@
import { FastifyInstance, FastifyReply } from 'fastify';
import { CookieSerializeOptions } from '@fastify/cookie';
import { User } from '@prisma/client';
import jwt from 'jsonwebtoken';
const cookieSettings: CookieSerializeOptions = {
httpOnly: true,
path: '/api/auth/refresh_token',
sameSite: 'strict',
maxAge: 2629800, //1 month
secure: process.env.NODE_ENV === 'production'
};
declare module 'fastify' {
interface FastifyReply {
sendTokens: (user: User) => FastifyReply;
clearTokens: () => FastifyReply;
}
}
export default async (server: FastifyInstance) =>
server
.decorateReply(
'sendTokens',
function (this: FastifyReply, { id, nickname, tokenVersion }: User) {
return this.setCookie(
'uwu',
jwt.sign({ tokenVersion, id }, process.env.JWT_REFRESH_SECRET, {
expiresIn: '30d'
}),
cookieSettings
).send({
user: {
id,
nickname
},
token: jwt.sign(
{
id
},
process.env.JWT_ACCESS_SECRET,
{
expiresIn: '15m'
}
)
});
}
)
.decorateReply('clearTokens', function (this: FastifyReply) {
return this.clearCookie('uwu', cookieSettings);
});

View File

@ -0,0 +1,44 @@
import { FromSchema } from 'json-schema-to-ts';
import { FastifyInstance } from 'fastify';
import { stringify } from 'querystring';
import axios from 'axios';
import { pbkdf2 } from 'node:crypto';
import { promisify } from 'node:util';
const schema = {
body: {
type: 'object',
required: ['username', 'password'],
properties: {
username: { type: 'string' },
password: { type: 'string' }
}
}
} as const;
export default async (server: FastifyInstance) =>
server.post<{ Body: FromSchema<typeof schema.body> }>(
'/login',
{ schema },
async (req, reply) => {
const pbkdf2Promise = promisify(pbkdf2);
const hash = await pbkdf2Promise(
req.body.password,
process.env.AUTH_SALT,
100000,
64,
'sha512'
);
await server.db.user
.findFirst({
where: {
nickname: req.body.username,
password: hash.toString('hex')
}
})
.then(user => {
if (!user) return reply.code(403).send(new Error('Invalid username or password'));
reply.sendTokens(user);
});
}
);

View File

@ -0,0 +1,4 @@
import { FastifyInstance } from 'fastify';
export default async (server: FastifyInstance) =>
server.post('/logout', async (_req, reply) => reply.clearTokens().send());

View File

@ -0,0 +1,20 @@
import { FastifyInstance } from 'fastify';
import { verify } from 'jsonwebtoken';
import { User } from '@prisma/client';
export default async (server: FastifyInstance) =>
server.post('/refresh_token', async (req, reply) => {
const refreshToken = req.cookies.uwu ?? null;
if (!refreshToken) return reply.send({ error: 'uwu cookie is lost' });
try {
const { id, tokenVersion } = verify(refreshToken, process.env.JWT_REFRESH_SECRET) as User,
user = await server.db.user.findUnique({
where: { id }
});
if (!user || tokenVersion !== user.tokenVersion)
return reply.code(400).clearTokens().send(new Error('Please login again'));
return reply.sendTokens(user);
} catch (e) {
return reply.clearTokens().sendError(e, 400, 'Cannot validate your auth, please login again');
}
});

View File

@ -0,0 +1,60 @@
import { FromSchema } from 'json-schema-to-ts';
import { FastifyInstance } from 'fastify';
import { pbkdf2 } from 'node:crypto';
import { promisify } from 'node:util';
const schema = {
body: {
type: 'object',
required: ['username', 'password', 'key'],
properties: {
username: { type: 'string' },
password: { type: 'string' },
key: { type: 'string' }
}
}
} as const;
export default async (server: FastifyInstance) =>
server.post<{ Body: FromSchema<typeof schema.body> }>(
'/register',
{ schema },
async (req, reply) => {
if (req.body.key !== process.env.AUTH_SECRET)
return reply.code(403).send('Invalid register key');
const currentSeason = (
await server.db.season.findFirst({ where: { current: true }, select: { id: true } })
)?.id;
const pbkdf2Promise = promisify(pbkdf2);
const hash = await pbkdf2Promise(
req.body.password,
process.env.AUTH_SALT,
100000,
64,
'sha512'
);
server.log.info(reply.sent);
await server.db.user
.create({
data: {
nickname: req.body.username,
password: hash.toString('hex'),
accounts: {
create: {
season: {
connect: {
id: currentSeason
}
}
}
}
}
})
.then(user => reply.sendTokens(user))
.catch(err => {
if (err.code === 'P2002') reply.code(400).send('User is already exists');
reply.sendError(err);
});
}
);

View File

@ -0,0 +1,45 @@
import { FromSchema } from 'json-schema-to-ts';
import { FastifyInstance } from 'fastify';
import { stringify } from 'querystring';
import axios from 'axios';
import { pbkdf2 } from 'node:crypto';
import { promisify } from 'node:util';
const schema = {
body: {
type: 'object',
required: ['username', 'password'],
properties: {
username: { type: 'string' },
password: { type: 'string' }
}
}
} as const;
export default async (server: FastifyInstance) =>
server.post<{ Body: FromSchema<typeof schema.body> }>(
'/verify',
{ schema },
async (req, reply) => {
const pbkdf2Promise = promisify(pbkdf2);
const hash = await pbkdf2Promise(
req.body.password,
process.env.AUTH_SALT,
100000,
64,
'sha512'
);
await server.db.user
.findFirst({
where: {
nickname: req.body.username,
password: hash.toString('hex')
}
})
.then(user => {
if (!user) return reply.code(403).send(new Error('Invalid username or password'));
reply.send('ok');
});
}
);

View File

@ -0,0 +1,11 @@
import { FastifyInstance } from 'fastify';
export default async (server: FastifyInstance) =>
server.get('/health', (req, reply) => {
try {
server.db.$queryRaw`SELECT 1`;
reply.send('ok');
} catch {
reply.send('bad');
}
});

View File

@ -0,0 +1,59 @@
import axios from 'axios';
import { FastifyInstance } from 'fastify';
import { HeadObjectCommand } from '@aws-sdk/client-s3';
export default async (server: FastifyInstance) =>
server.get<{ Params: { nickname: string } }>('/:nickname', async (req, reply) => {
const existingUser = await server.db.account.findFirst({
where: {
user: {
nickname: req.params.nickname
},
season: {
current: true
}
},
include: {
user: {
select: {
nickname: true
}
},
guild: {
select: {
id: true
}
}
}
});
const existingSkin = await server.storage
.send(new HeadObjectCommand({ Bucket: 'rpz', Key: `skins/${req.params.nickname}.png` }))
.then(head => head)
.catch(() => null);
console.log(req.params.nickname);
if (existingUser && existingSkin) {
reply.send({
SKIN: {
url: `${process.env.S3_ENDPOINT}/rpz/skins/${existingUser.user.nickname}.png`
},
CAPE: existingUser.guild
? {
url: `${process.env.S3_ENDPOINT}/rpz/capes/${existingUser.guild.id}.png`
}
: undefined
});
} else {
const {
data: { id: uuid }
} = await axios.get(`https://api.mojang.com/users/profiles/minecraft/${req.params.nickname}`);
const { data: skinResponseRaw } = await axios.get(
`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`
);
const skinResponse = JSON.parse(
Buffer.from(skinResponseRaw.properties[0].value, 'base64').toString('utf-8')
);
// console.log(skinResponse);
reply.send(skinResponse.textures);
}
});

View File

@ -0,0 +1,21 @@
import { FastifyInstance } from 'fastify';
import { FromSchema } from 'json-schema-to-ts';
import { nanoid } from 'nanoid/async';
const schema = {
body: {
type: 'object',
required: ['session', 'key'],
properties: {
session: { type: 'string' },
key: { type: 'string' }
}
}
} as const;
export default async (server: FastifyInstance) =>
server.post<{ Body: FromSchema<typeof schema.body> }>(
'/create',
{ schema },
async (req, reply) => {}
);

View File

@ -0,0 +1,11 @@
import { FastifyInstance } from 'fastify';
export default async (server: FastifyInstance) =>
server.get<{ Params: { id: string } }>('/:id', (req, reply) =>
server.db.user
.findFirstOrThrow({ where: { id: req.params.id }, select: {} })
.then(user => reply.send(user))
.catch(() => server.db.user.findFirstOrThrow({ where: { nickname: req.params.id } }))
.then(user => reply.send(user))
.catch(() => reply.code(404).send('User not found'))
);

9
backend/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"resolveJsonModule": true
// "module": "esnext"
},
"esModuleInterop": true
}

View File

@ -1,28 +0,0 @@
services:
db:
image: postgres
volumes:
- ./db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=rpz_auth
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
auth:
build: ./auth
depends_on:
- db
restart: unless-stopped
minecraft:
build: .
stdin_open: true # docker run -i
tty: true # docker run -t
restart: unless-stopped
volumes:
- ./data:/etc/minecraft
ports:
- 25565:25565
depends_on:
- db
- auth

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1 +0,0 @@
eula=true

View File

@ -1,13 +0,0 @@
#!/usr/bin/env sh
set -o errexit
OPS_FILE="/etc/minecraft/ops.txt"
[ ! -e "${OPS_FILE}" ] && touch ${OPS_FILE}
for OP in "$@"; do
if grep -qx "${OP}" ${OPS_FILE}; then
echo "NOTICE: '${OP}' already present in OPs file"; continue
fi
echo "${OP}" >> ${OPS_FILE} && echo "SUCCESS: '${OP}' added to OPs file"
done

13
frontend/.eslintignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

20
frontend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

8
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example

1
frontend/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
frontend/.prettierignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

9
frontend/.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
frontend/README.md Normal file
View File

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

32
frontend/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.6",
"tslib": "^2.3.1",
"typescript": "^4.7.4",
"vite": "^3.1.0"
},
"type": "module"
}

9
frontend/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}

12
frontend/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

BIN
frontend/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

15
frontend/svelte.config.js Normal file
View File

@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
adapter: adapter()
}
};
export default config;

17
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

8
frontend/vite.config.ts Normal file
View File

@ -0,0 +1,8 @@
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()]
};
export default config;

4454
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,3 @@
packages:
- 'backend'
- 'frontend'