redesign
parent
dd1aab37d0
commit
8ec3e6ff55
|
@ -1,4 +1 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
README.md
|
||||
node_modules/
|
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
README.md
|
|
@ -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"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "hasPass" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -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;
|
|
@ -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";
|
|
@ -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");
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "tokenVersion" INTEGER NOT NULL DEFAULT 0;
|
|
@ -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;
|
|
@ -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";
|
|
@ -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;
|
|
@ -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";
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "password" TEXT;
|
|
@ -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
|
||||
// }
|
|
@ -1,17 +1,28 @@
|
|||
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 {
|
||||
|
@ -25,6 +36,11 @@ declare global {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +50,18 @@ fastify({
|
|||
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
|
||||
|
@ -49,18 +77,30 @@ fastify({
|
|||
// 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' }
|
||||
})
|
||||
// .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'))
|
|
@ -20,30 +20,32 @@ declare module 'fastify' {
|
|||
|
||||
export default async (server: FastifyInstance) =>
|
||||
server
|
||||
.decorateReply('sendTokens', function (this: FastifyReply, { id, nickname }: User) {
|
||||
return this.setCookie(
|
||||
'owo',
|
||||
jwt.sign({ tokenVersion: 0, id }, process.env.JWT_REFRESH_SECRET, {
|
||||
expiresIn: '30d'
|
||||
}),
|
||||
cookieSettings
|
||||
).send({
|
||||
user: {
|
||||
id,
|
||||
nickname
|
||||
},
|
||||
token: jwt.sign(
|
||||
{
|
||||
.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
|
||||
},
|
||||
process.env.JWT_ACCESS_SECRET,
|
||||
{
|
||||
expiresIn: '15m'
|
||||
}
|
||||
)
|
||||
});
|
||||
})
|
||||
token: jwt.sign(
|
||||
{
|
||||
id
|
||||
},
|
||||
process.env.JWT_ACCESS_SECRET,
|
||||
{
|
||||
expiresIn: '15m'
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
)
|
||||
.decorateReply('clearTokens', function (this: FastifyReply) {
|
||||
return this.clearCookie('owo', cookieSettings);
|
||||
return this.clearCookie('uwu', cookieSettings);
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,4 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
export default async (server: FastifyInstance) =>
|
||||
server.post('/logout', async (_req, reply) => reply.clearTokens().send());
|
|
@ -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');
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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) => {}
|
||||
);
|
|
@ -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'))
|
||||
);
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"resolveJsonModule": true
|
||||
// "module": "esnext"
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
"esModuleInterop": true
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
|
@ -0,0 +1 @@
|
|||
engine-strict=true
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
|
@ -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 {}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -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;
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import type { UserConfig } from 'vite';
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
export default config;
|
40
package.json
40
package.json
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"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/node": "^18.7.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.0",
|
||||
"@typescript-eslint/parser": "^5.35.1",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/autoload": "^5.2.0",
|
||||
"@fastify/cookie": "^8.1.0",
|
||||
"@prisma/client": "^4.2.1",
|
||||
"axios": "^0.27.2",
|
||||
"fastify": "^4.5.3",
|
||||
"json-schema-to-ts": "^2.5.5",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"nanoid": "3",
|
||||
"prisma": "^4.3.0",
|
||||
"redis": "^4.3.0",
|
||||
"simple-oauth2": "^4.3.0"
|
||||
}
|
||||
}
|
3294
pnpm-lock.yaml
3294
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
|||
packages:
|
||||
- 'backend'
|
||||
- 'frontend'
|
|
@ -1,29 +0,0 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
nickname String? @unique
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import('nanoid').then(nanoid => {
|
||||
const db = new PrismaClient();
|
||||
|
||||
db.session
|
||||
.create({
|
||||
data: {
|
||||
id: nanoid.nanoid(10),
|
||||
ip: '127.0.0.1',
|
||||
nickname: 'Qugalet',
|
||||
expiredAfter: new Date(Date.now() + 604800)
|
||||
}
|
||||
})
|
||||
.then(session =>
|
||||
console.log(
|
||||
encodeURI(
|
||||
`https://auth.m0e.space/application/o/authorize/?client_id=7f24f967b2faf3d8c1c53771661abff8e07060c0&response_type=code&redirect_uri=http://localhost:8080/api/auth/verify?session=${session.id}&scope=profile openid`
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { nanoid } from 'nanoid/async';
|
||||
|
||||
const schema = {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
required: ['session', 'key'],
|
||||
properties: {
|
||||
session: { type: 'string' },
|
||||
key: { type: 'string' }
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
export default async (server: FastifyInstance) =>
|
||||
server.get<{ Querystring: FromSchema<typeof schema.querystring> }>(
|
||||
'/create',
|
||||
async (req, reply) => {
|
||||
if (req.query.key !== process.env.URL_TOKEN) return reply.code(403).send('Forbidden');
|
||||
const session = await server.db.session.findFirst({
|
||||
where: {
|
||||
id: req.query.session
|
||||
}
|
||||
});
|
||||
if (!session) return reply.code(404).send('Session is not found');
|
||||
const linkedSession = await server.db.link.findFirst({
|
||||
where: { session: { id: req.query.session } },
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
if (linkedSession)
|
||||
reply.send(
|
||||
`${
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://mc.m0e.space/a'
|
||||
: 'http://localhost:8080/a'
|
||||
}/${linkedSession.id}`
|
||||
);
|
||||
let linkId = await nanoid(5);
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const link = await server.db.link.findFirst({
|
||||
where: { id: linkId },
|
||||
select: { id: true }
|
||||
});
|
||||
if (!link) break;
|
||||
linkId = await nanoid(5);
|
||||
}
|
||||
await server.db.link.create({
|
||||
data: {
|
||||
id: linkId,
|
||||
link: encodeURI(
|
||||
`https://auth.m0e.space/application/o/authorize/?client_id=7f24f967b2faf3d8c1c53771661abff8e07060c0&response_type=code&redirect_uri=${
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://mc.m0e.space'
|
||||
: 'http://localhost:8080'
|
||||
}/api/auth/verify?session=${req.query.session}&scope=profile openid`
|
||||
),
|
||||
session: {
|
||||
connect: {
|
||||
id: req.query.session
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
reply.send(
|
||||
`${
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://mc.m0e.space/a'
|
||||
: 'http://localhost:8080/a'
|
||||
}/${linkId}`
|
||||
);
|
||||
}
|
||||
);
|
|
@ -1,9 +0,0 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
export default async (server: FastifyInstance) =>
|
||||
server.get<{ Params: { id: string } }>('/:id', (req, reply) =>
|
||||
server.db.link
|
||||
.findFirstOrThrow({ where: { id: req.params.id } })
|
||||
.then(link => reply.redirect(link.link))
|
||||
.catch(() => reply.code(404).send('URL not found'))
|
||||
);
|
|
@ -1,88 +0,0 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { stringify } from 'querystring';
|
||||
import axios from 'axios';
|
||||
|
||||
const schema = {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
required: ['code', 'session'],
|
||||
properties: {
|
||||
code: { type: 'string' },
|
||||
session: { type: 'string' }
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
export default async (server: FastifyInstance) =>
|
||||
server.get<{ Querystring: FromSchema<typeof schema.querystring> }>(
|
||||
'/verify',
|
||||
{ schema },
|
||||
(req, reply) => {
|
||||
axios
|
||||
.post(
|
||||
'https://auth.m0e.space/application/o/token/',
|
||||
stringify({
|
||||
client_id: process.env.AUTH_CLIENT_ID,
|
||||
client_secret: process.env.AUTH_CLIENT_SECRET,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://mc.m0e.space/api/auth/verify'
|
||||
: 'http://localhost:8080/api/auth/verify',
|
||||
code: req.query.code
|
||||
})
|
||||
)
|
||||
.then(res =>
|
||||
axios
|
||||
.get('https://auth.m0e.space/application/o/userinfo/', {
|
||||
headers: { Authorization: `Bearer ${res.data.access_token}` }
|
||||
})
|
||||
.then(async res => {
|
||||
const user =
|
||||
(await server.db.user.findFirst({ where: { id: res.data.sub } })) ||
|
||||
(await server.db.user.create({ data: { id: res.data.sub } }));
|
||||
const session = await server.db.session.findFirst({
|
||||
where: { id: req.query.session }
|
||||
});
|
||||
if (!session) return reply.code(400).send('Invalid session');
|
||||
const userByNickname = await server.db.user.findFirst({
|
||||
where: { nickname: session.nickname }
|
||||
});
|
||||
if (!userByNickname) {
|
||||
await Promise.all([
|
||||
server.db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { nickname: session.nickname }
|
||||
}),
|
||||
server.db.session.update({
|
||||
where: { id: req.query.session },
|
||||
data: {
|
||||
verified: true
|
||||
}
|
||||
})
|
||||
]).then(() => reply.redirect('https://mc.m0e.space/message/success'));
|
||||
|
||||
// await server.redis.publish('rpz_auth', req.query.session);
|
||||
} else if (userByNickname.id !== user.id) reply.send(403).send('Forbidden');
|
||||
else
|
||||
await server.db.session
|
||||
.update({
|
||||
where: { id: req.query.session },
|
||||
data: {
|
||||
verified: true
|
||||
}
|
||||
})
|
||||
.then(() => reply.redirect('https://mc.m0e.space/message/success'));
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
reply.code(500).send(err);
|
||||
})
|
||||
)
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
reply.code(500).send(err);
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue