mirror of
https://github.com/YuzuZensai/Minikura.git
synced 2026-01-31 14:57:49 +00:00
✨ feat: Servers feature in backend
This commit is contained in:
@@ -1,20 +1,418 @@
|
|||||||
import { dotenvLoad } from "dotenv-mono";
|
import { dotenvLoad } from "dotenv-mono";
|
||||||
const dotenv = dotenvLoad();
|
const dotenv = dotenvLoad();
|
||||||
|
|
||||||
import { Elysia } from "elysia";
|
import { Elysia, error, t } from "elysia";
|
||||||
import { swagger } from "@elysiajs/swagger";
|
import { swagger } from "@elysiajs/swagger";
|
||||||
import { prisma } from "@minikura/db";
|
import { prisma, ServerType } from "@minikura/db";
|
||||||
|
|
||||||
|
import { ServerService } from "./services/server";
|
||||||
|
import { UserService } from "./services/user";
|
||||||
|
import argon2 from "argon2";
|
||||||
|
import { SessionService } from "./services/session";
|
||||||
|
|
||||||
|
enum ReturnError {
|
||||||
|
INVALID_USERNAME_OR_PASSWORD = "INVALID_USERNAME_OR_PASSWORD",
|
||||||
|
MISSING_TOKEN = "MISSING_TOKEN",
|
||||||
|
REVOKED_TOKEN = "REVOKED_TOKEN",
|
||||||
|
EXPIRED_TOKEN = "EXPIRED_TOKEN",
|
||||||
|
INVALID_TOKEN = "INVALID_TOKEN",
|
||||||
|
SERVER_NAME_IN_USE = "SERVER_NAME_IN_USE",
|
||||||
|
SERVER_NOT_FOUND = "SERVER_NOT_FOUND",
|
||||||
|
}
|
||||||
|
|
||||||
|
const bootstrap = async () => {
|
||||||
|
const users = await prisma.user.findMany();
|
||||||
|
if (users.length !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: "admin",
|
||||||
|
password: await argon2.hash("admin"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Default user created");
|
||||||
|
};
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(swagger())
|
.use(swagger())
|
||||||
.get("/", async () => {
|
.ws("/ws", {
|
||||||
return "Hello Elysia";
|
body: t.Object({
|
||||||
|
message: t.String(),
|
||||||
|
}),
|
||||||
|
// query: t.Object({
|
||||||
|
// id: t.String(),
|
||||||
|
// }),
|
||||||
|
message(ws, { message }) {
|
||||||
|
const { id } = ws.data.query;
|
||||||
|
ws.send({
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
time: Date.now(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.post(
|
||||||
|
"/login",
|
||||||
|
async ({ body, cookie: { session_token } }) => {
|
||||||
|
const user = await UserService.getUserByUsername(body.username);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// Fake hash to prevent timing attacks
|
||||||
|
const FAKE_HASH =
|
||||||
|
"$argon2id$v=19$m=65536,t=3,p=4$PMqZArEBmfkHPbPv7Z50KQ$miV6tIIXU6w2WWOGLbWrdan8TGMTUsS1A1hy9RrpS9Y";
|
||||||
|
await argon2.verify(FAKE_HASH, "fake");
|
||||||
|
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_USERNAME_OR_PASSWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await argon2.verify(user.password, body.password);
|
||||||
|
if (!valid) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_USERNAME_OR_PASSWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await SessionService.create(user.id);
|
||||||
|
|
||||||
|
session_token.httpOnly = true;
|
||||||
|
session_token.value = session.token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
username: t.String({ minLength: 1 }),
|
||||||
|
password: t.String({ minLength: 1 }),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.derive(async ({ headers, cookie: { session_token } }) => {
|
||||||
|
const auth = session_token.value;
|
||||||
|
const token = headers.authorization?.split(" ")[1];
|
||||||
|
|
||||||
|
if (!auth && !token)
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.MISSING_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
const session = await SessionService.validate(auth);
|
||||||
|
if (session.status === SessionService.SESSION_STATUS.REVOKED) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.REVOKED_TOKEN,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (session.status === SessionService.SESSION_STATUS.EXPIRED) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.EXPIRED_TOKEN,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
session.status === SessionService.SESSION_STATUS.INVALID ||
|
||||||
|
session.status !== SessionService.SESSION_STATUS.VALID ||
|
||||||
|
!session.session
|
||||||
|
) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_TOKEN,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
server: null,
|
||||||
|
session: session.session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const session = await SessionService.validateApiKey(token);
|
||||||
|
if (session.status === SessionService.SESSION_STATUS.INVALID) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_TOKEN,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
session.status === SessionService.SESSION_STATUS.VALID &&
|
||||||
|
session.server
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
session: null,
|
||||||
|
server: session.server,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never reach here
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_TOKEN,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.post("/logout", async ({ session, cookie: { session_token } }) => {
|
||||||
|
if (!session) return { success: true };
|
||||||
|
|
||||||
|
await SessionService.revoke(session.token);
|
||||||
|
|
||||||
|
session_token.remove();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.get("/servers", async ({ session }) => {
|
||||||
|
return await ServerService.getAllServers(!session);
|
||||||
|
})
|
||||||
|
.get("/servers/:id", async ({ session, params: { id } }) => {
|
||||||
|
return await ServerService.getServerById(id, !session);
|
||||||
|
})
|
||||||
|
.post(
|
||||||
|
"/servers",
|
||||||
|
async ({ body, error }) => {
|
||||||
|
// Must be a-z, A-Z, 0-9, and -_ only
|
||||||
|
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
||||||
|
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
||||||
|
}
|
||||||
|
|
||||||
|
const _server = await ServerService.getServerByName(body.name);
|
||||||
|
if (_server) {
|
||||||
|
return error("Conflict", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NAME_IN_USE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = await ServerService.createServer({
|
||||||
|
name: body.name,
|
||||||
|
description: body.description,
|
||||||
|
address: body.address,
|
||||||
|
port: body.port,
|
||||||
|
type: body.type,
|
||||||
|
join_priority: body.join_priority,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
server,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String({ minLength: 1 }),
|
||||||
|
description: t.Nullable(t.String({ minLength: 1 })),
|
||||||
|
address: t.String({ minLength: 1 }),
|
||||||
|
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||||
|
type: t.Enum(ServerType),
|
||||||
|
join_priority: t.Nullable(t.Integer({ minimum: 0 })),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.patch(
|
||||||
|
"/servers/:id",
|
||||||
|
async ({ session, params: { id }, body }) => {
|
||||||
|
const server = await ServerService.getServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.name) {
|
||||||
|
const _server = await ServerService.getServerByName(body.name);
|
||||||
|
if (_server && _server.id !== id) {
|
||||||
|
return error("Conflict", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NAME_IN_USE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: body.name,
|
||||||
|
description: body.description,
|
||||||
|
address: body.address,
|
||||||
|
port: body.port,
|
||||||
|
join_priority: body.join_priority,
|
||||||
|
};
|
||||||
|
|
||||||
|
await prisma.server.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newServer = await ServerService.getServerById(id, !session);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
server: newServer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.Optional(t.String({ minLength: 1 })),
|
||||||
|
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||||
|
address: t.Optional(t.String({ minLength: 1 })),
|
||||||
|
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
|
join_priority: t.Optional(t.Integer({ minimum: 0 })),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete("/servers/:id", async ({ params: { id } }) => {
|
||||||
|
const server = await ServerService.getServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.server.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.get("/reverse_proxy_servers", async ({ session }) => {
|
||||||
|
return await ServerService.getAllReverseProxyServers(!session);
|
||||||
|
})
|
||||||
|
.post(
|
||||||
|
"/reverse_proxy_servers",
|
||||||
|
async ({ body, error }) => {
|
||||||
|
// Must be a-z, A-Z, 0-9, and -_ only
|
||||||
|
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
||||||
|
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
||||||
|
}
|
||||||
|
|
||||||
|
const _server = await ServerService.getReverseProxyServerByName(
|
||||||
|
body.name
|
||||||
|
);
|
||||||
|
if (_server) {
|
||||||
|
return error("Conflict", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NAME_IN_USE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = await ServerService.createReverseProxyServer({
|
||||||
|
name: body.name,
|
||||||
|
address: body.address,
|
||||||
|
port: body.port,
|
||||||
|
description: body.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
server,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String({ minLength: 1 }),
|
||||||
|
description: t.Nullable(t.String({ minLength: 1 })),
|
||||||
|
address: t.String({ minLength: 1 }),
|
||||||
|
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.patch(
|
||||||
|
"/reverse_proxy_servers/:id",
|
||||||
|
async ({ session, params: { id }, body }) => {
|
||||||
|
const server = await prisma.reverseProxyServer.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.name) {
|
||||||
|
const _server = await ServerService.getServerByName(body.name);
|
||||||
|
if (_server && _server.id !== id) {
|
||||||
|
return error("Conflict", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NAME_IN_USE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: body.name,
|
||||||
|
description: body.description,
|
||||||
|
address: body.address,
|
||||||
|
port: body.port,
|
||||||
|
};
|
||||||
|
|
||||||
|
await prisma.reverseProxyServer.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newServer = await ServerService.getReverseProxyServerById(
|
||||||
|
id,
|
||||||
|
!session
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
server: newServer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
name: t.Optional(t.String({ minLength: 1 })),
|
||||||
|
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||||
|
address: t.Optional(t.String({ minLength: 1 })),
|
||||||
|
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete("/reverse_proxy_servers/:id", async ({ params: { id } }) => {
|
||||||
|
const server = await prisma.reverseProxyServer.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.reverseProxyServer.delete({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.get("/hello", "Do you miss me?")
|
|
||||||
.listen(3000, async () => {
|
.listen(3000, async () => {
|
||||||
console.log("Server is running on port 3000");
|
console.log("Server is running on port 3000");
|
||||||
const result = await prisma.server.findMany();
|
bootstrap();
|
||||||
console.log(result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type App = typeof app;
|
export type App = typeof app;
|
||||||
|
|||||||
127
apps/backend/src/services/server.ts
Normal file
127
apps/backend/src/services/server.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { prisma } from "@minikura/db";
|
||||||
|
import type { ServerType } from "@minikura/db";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
export namespace ServerService {
|
||||||
|
export async function getAllServers(omitSensitive = false) {
|
||||||
|
return await prisma.server.findMany({
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllReverseProxyServers(omitSensitive = false) {
|
||||||
|
return await prisma.reverseProxyServer.findMany({
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerById(id: string, omitSensitive = false) {
|
||||||
|
return await prisma.server.findUnique({
|
||||||
|
where: { id },
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReverseProxyServerById(
|
||||||
|
id: string,
|
||||||
|
omitSensitive = false
|
||||||
|
) {
|
||||||
|
return await prisma.reverseProxyServer.findUnique({
|
||||||
|
where: { id },
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerByName(name: string, omitSensitive = false) {
|
||||||
|
return await prisma.server.findUnique({
|
||||||
|
where: { name },
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReverseProxyServerByName(
|
||||||
|
name: string,
|
||||||
|
omitSensitive = false
|
||||||
|
) {
|
||||||
|
return await prisma.reverseProxyServer.findUnique({
|
||||||
|
where: { name },
|
||||||
|
omit: {
|
||||||
|
api_key: omitSensitive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createReverseProxyServer({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
address: string;
|
||||||
|
port: number;
|
||||||
|
}) {
|
||||||
|
let token = crypto.randomBytes(64).toString("hex");
|
||||||
|
token = token
|
||||||
|
.split("")
|
||||||
|
.map((char) => (Math.random() > 0.5 ? char.toUpperCase() : char))
|
||||||
|
.join("");
|
||||||
|
token = `minikura_reverse_proxy_server_api_key_${token}`;
|
||||||
|
|
||||||
|
return await prisma.reverseProxyServer.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
api_key: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createServer({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
type,
|
||||||
|
join_priority,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
address: string;
|
||||||
|
port: number;
|
||||||
|
type: ServerType;
|
||||||
|
join_priority: number | null;
|
||||||
|
}) {
|
||||||
|
let token = crypto.randomBytes(64).toString("hex");
|
||||||
|
token = token
|
||||||
|
.split("")
|
||||||
|
.map((char) => (Math.random() > 0.5 ? char.toUpperCase() : char))
|
||||||
|
.join("");
|
||||||
|
token = `minikura_server_api_key_${token}`;
|
||||||
|
|
||||||
|
return await prisma.server.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
type,
|
||||||
|
api_key: token,
|
||||||
|
join_priority,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
126
apps/backend/src/services/session.ts
Normal file
126
apps/backend/src/services/session.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { prisma } from "@minikura/db";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
export namespace SessionService {
|
||||||
|
export enum SESSION_STATUS {
|
||||||
|
VALID = "VALID",
|
||||||
|
INVALID = "INVALID",
|
||||||
|
REVOKED = "REVOKED",
|
||||||
|
EXPIRED = "EXPIRED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validate(token: string) {
|
||||||
|
const session = await prisma.session.findUnique({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.INVALID,
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.revoked) {
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.REVOKED,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.expires_at < new Date()) {
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.EXPIRED,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.VALID,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateApiKey(apiKey: string) {
|
||||||
|
// If starts with "minikura_reverse_proxy_server_api_key_"
|
||||||
|
if (apiKey.startsWith("minikura_reverse_proxy_server_api_key_")) {
|
||||||
|
const reverseProxyServer = await prisma.reverseProxyServer.findUnique({
|
||||||
|
where: {
|
||||||
|
api_key: apiKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!reverseProxyServer) {
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.INVALID,
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.VALID,
|
||||||
|
server: reverseProxyServer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey.startsWith("minikura_server_api_key_")) {
|
||||||
|
const server = await prisma.server.findUnique({
|
||||||
|
where: {
|
||||||
|
api_key: apiKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.INVALID,
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.VALID,
|
||||||
|
server: server,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: SESSION_STATUS.INVALID,
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(userId: string) {
|
||||||
|
let token = crypto.randomBytes(64).toString("hex");
|
||||||
|
token = token
|
||||||
|
.split("")
|
||||||
|
.map((char) => (Math.random() > 0.5 ? char.toUpperCase() : char))
|
||||||
|
.join("");
|
||||||
|
token = `minikura_user_session_${token}`;
|
||||||
|
|
||||||
|
return await prisma.session.create({
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Expires in 48 hours
|
||||||
|
expires_at: new Date(Date.now() + 48 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function revoke(token: string) {
|
||||||
|
return await prisma.session.update({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
revoked: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
9
apps/backend/src/services/user.ts
Normal file
9
apps/backend/src/services/user.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { prisma } from "@minikura/db";
|
||||||
|
|
||||||
|
export namespace UserService {
|
||||||
|
export async function getUserByUsername(username: string) {
|
||||||
|
return await prisma.user.findUnique({
|
||||||
|
where: { username },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["omitApi"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
|
|||||||
Reference in New Issue
Block a user