diff --git a/routes/NextNet API/Log In User.bru b/routes/NextNet API/Log In User.bru new file mode 100644 index 0000000..138fce1 --- /dev/null +++ b/routes/NextNet API/Log In User.bru @@ -0,0 +1,18 @@ +meta { + name: Create User + type: http + seq: 1 +} + +post { + url: http://127.0.0.1:3000/api/v1/users/login + body: json + auth: none +} + +body:json { + { + "email": "me@greysoh.dev", + "password": "password" + } +} diff --git a/src/index.ts b/src/index.ts index b743692..3603a3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import Fastify from "fastify"; import { ServerOptions, SessionToken } from "./libs/types.js"; import { route as create } from "./routes/user/create.js"; +import { route as login } from "./routes/user/login.js"; const prisma = new PrismaClient(); @@ -31,6 +32,7 @@ const fastify = Fastify({ }); create(fastify, prisma, sessionTokens, serverOptions); +login(fastify, prisma, sessionTokens, serverOptions); // Run the server! try { diff --git a/src/libs/permissions.ts b/src/libs/permissions.ts index 1709dbb..385d7b7 100644 --- a/src/libs/permissions.ts +++ b/src/libs/permissions.ts @@ -1,4 +1,5 @@ import type { PrismaClient } from "@prisma/client"; +import type { SessionToken } from "./types.js"; export const permissionListDisabled: Record = { "routes.add": false, @@ -29,14 +30,44 @@ for (const index of Object.keys(permissionListEnabled)) { permissionListEnabled[index] = true; } -export async function hasPermission(permission: string, uid: number, prisma: PrismaClient): Promise { - const permissionNode = await prisma.permission.findFirst({ - where: { - userID: uid, - permission - } - }); +export async function hasPermission(permissionList: string[], uid: number, prisma: PrismaClient): Promise { + for (const permission of permissionList) { + const permissionNode = await prisma.permission.findFirst({ + where: { + userID: uid, + permission + } + }); - if (!permissionNode) return false; - return permissionNode.has; + if (!permissionNode || !permissionNode.has) return false; + } + + return true; +} + +export async function hasPermissionByToken(permissionList: string[], token: string, tokens: Record, prisma: PrismaClient): Promise { + let userID = -1; + + for (const otherTokenKey of Object.keys(tokens)) { + const otherTokenList = tokens[parseInt(otherTokenKey)]; + + for (const otherTokenIndex in otherTokenList) { + const otherToken = otherTokenList[otherTokenIndex]; + + if (otherToken.token == token) { + if (otherToken.expiresAt < otherToken.createdAt + (otherToken.createdAt - Date.now())) { + otherTokenList.splice(parseInt(otherTokenIndex), 1); + continue; + } else { + userID = parseInt(otherTokenKey); + } + } + } + } + + // Fine, we'll look up for global tokens... + + if (userID == -1) return false; + + return true; } \ No newline at end of file diff --git a/src/routes/ROUTE_PLAN.md b/src/routes/ROUTE_PLAN.md new file mode 100644 index 0000000..a2ea92f --- /dev/null +++ b/src/routes/ROUTE_PLAN.md @@ -0,0 +1,12 @@ +# Route Plan +- /api/v1/users/create +- /api/v1/users/disable +- /api/v1/users/remove +- /api/v1/backends/create +- /api/v1/backends/remove +- /api/v1/backends/modify +- /api/v1/backends/search +- /api/v1/routes/create +- /api/v1/routes/remove +- /api/v1/routes/modify +- /api/v1/routes/search \ No newline at end of file diff --git a/src/routes/user/create.ts b/src/routes/user/create.ts index a22cdd2..6f80e80 100644 --- a/src/routes/user/create.ts +++ b/src/routes/user/create.ts @@ -7,8 +7,6 @@ import { permissionListEnabled } from "../../libs/permissions.js"; import { generateToken } from "../../libs/generateToken.js"; export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Record, options: ServerOptions) { - // TODO: Permissions - /** * Creates a new user account to use, only if it is enabled. */ @@ -63,9 +61,7 @@ export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Re permission: string, has: boolean }[] - }, - - rootToken: null + } }; // TODO: There's probably a faster way to pull this off, but I'm lazy @@ -79,6 +75,7 @@ export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Re }; if (options.allowUnsafeGlobalTokens) { + // @ts-ignore userData.rootToken = generateToken() as unknown as null; } diff --git a/src/routes/user/login.ts b/src/routes/user/login.ts new file mode 100644 index 0000000..c5ae933 --- /dev/null +++ b/src/routes/user/login.ts @@ -0,0 +1,62 @@ +import type { PrismaClient } from "@prisma/client"; +import type { FastifyInstance } from "fastify"; +import { compare } from "bcrypt"; + +import { ServerOptions, SessionToken } from "../../libs/types.js"; +import { generateToken } from "../../libs/generateToken.js"; + +export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Record, options: ServerOptions) { + /** + * Logs in to a user account. + */ + fastify.post("/api/v1/users/login", { + schema: { + body: { + type: "object", + required: ["email", "password"], + + properties: { + email: { type: "string" }, + password: { type: "string" } + } + } + } + }, async(req, res) => { + // @ts-ignore + const body: { + email: string, + password: string + } = req.body; + + const userSearch = await prisma.user.findFirst({ + where: { + email: body.email + } + }); + + if (!userSearch) return res.status(403).send({ + error: "Email or password is incorrect" + }); + + const passwordIsValid = compare(userSearch.password, body.password); + + if (!passwordIsValid) return res.status(403).send({ + error: "Email or password is incorrect" + }); + + const token = generateToken(); + if (!tokens[userSearch.id]) tokens[userSearch.id] = []; + + tokens[userSearch.id].push({ + createdAt: Date.now(), + expiresAt: Date.now() + (30 * 60_000), + + token + }); + + return { + success: true, + token + } + }); +} \ No newline at end of file