From 6671323ff30dcde45b117ab267de2d4df9f65cab Mon Sep 17 00:00:00 2001 From: zkldi Date: Wed, 26 May 2021 17:24:42 +0100 Subject: [PATCH] Add util for assigning req-ktchi-data --- server/src/server/router/api/v1/auth/auth.ts | 3 +- server/src/server/router/ir/beatoraja/auth.ts | 41 +++++++++++++++ .../router/ir/beatoraja/charts/router.ts | 24 ++++++++- .../src/server/router/ir/beatoraja/router.ts | 51 +++++++++++++++++-- server/src/utils/misc.ts | 9 ++++ server/src/utils/req-ktchi-data.ts | 12 +++++ 6 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 server/src/server/router/ir/beatoraja/auth.ts create mode 100644 server/src/utils/req-ktchi-data.ts diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index 57fc8da96..4ef8126e3 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -26,8 +26,7 @@ export function CreateAPIKey(): string { } /** - * Despite these functions doing ultimately the same thing, they're separate incase they ever need to, - * you know, not do that. + * Despite these functions doing ultimately the same thing, they're separate incase they ever need to return something different. * @returns A string */ export function CreateInviteCode(): string { diff --git a/server/src/server/router/ir/beatoraja/auth.ts b/server/src/server/router/ir/beatoraja/auth.ts new file mode 100644 index 000000000..74b40233e --- /dev/null +++ b/server/src/server/router/ir/beatoraja/auth.ts @@ -0,0 +1,41 @@ +import { GenericAuthDocument } from "kamaitachi-common"; +import { RequestHandler } from "express"; +import db from "../../../../external/mongo/db"; +import { SYMBOL_KtchiData } from "../../../../lib/constants/ktchi"; +import { SplitAuthorizationHeader } from "../../../../utils/misc"; +import { AssignToReqKtchiData } from "../../../../utils/req-ktchi-data"; + +const ValidateAuthToken: RequestHandler = async (req, res, next) => { + const header = req.header("Authorization"); + + if (!header) { + return res.status(400).json({ + success: false, + description: `No Authorization provided.`, + }); + } + + const { type, token } = SplitAuthorizationHeader(header); + + if (type !== "Bearer") { + return res.status(400).json({ + success: false, + description: `Invalid Authorization Type.`, + }); + } + + const beatorajaAuthDoc = (await db["beatoraja-auth-tokens"].find({ + token, + })) as GenericAuthDocument | null; + + if (!beatorajaAuthDoc) { + return res.status(401).json({ + success: false, + description: "Unauthorised.", + }); + } + + AssignToReqKtchiData(req, { beatorajaAuthDoc }); + + return next(); +}; diff --git a/server/src/server/router/ir/beatoraja/charts/router.ts b/server/src/server/router/ir/beatoraja/charts/router.ts index 215e37ce6..21ab0a307 100644 --- a/server/src/server/router/ir/beatoraja/charts/router.ts +++ b/server/src/server/router/ir/beatoraja/charts/router.ts @@ -1,11 +1,31 @@ -import { PBScoreDocument } from "kamaitachi-common"; -import { Router } from "express"; +import { ChartDocument, PBScoreDocument } from "kamaitachi-common"; +import { Router, RequestHandler } from "express"; import db from "../../../../../external/mongo/db"; import { SYMBOL_KtchiData } from "../../../../../lib/constants/ktchi"; import { KtchiPBScoreToBeatorajaFormat } from "./convert-scores"; +import { AssignToReqKtchiData } from "../../../../../utils/req-ktchi-data"; const router: Router = Router({ mergeParams: true }); +const GetChartDocument: RequestHandler = async (req, res, next) => { + const chart = (await db.charts.bms.findOne({ + "data.chartSHA256": req.params.chartSHA256, + })) as ChartDocument<"bms:7K" | "bms:14K"> | null; + + if (!chart) { + return res.status(404).json({ + success: false, + description: `Chart does not exist on IR yet.`, + }); + } + + AssignToReqKtchiData(req, { beatorajaChartDoc: chart }); + + return next(); +}; + +router.use(GetChartDocument); + /** * Retrieves scores for the given chart. * @name GET /ir/beatoraja/chart/:chartSHA256/scores diff --git a/server/src/server/router/ir/beatoraja/router.ts b/server/src/server/router/ir/beatoraja/router.ts index 817871e64..a74ee6e14 100644 --- a/server/src/server/router/ir/beatoraja/router.ts +++ b/server/src/server/router/ir/beatoraja/router.ts @@ -1,20 +1,63 @@ import { Router } from "express"; +import db from "../../../../external/mongo/db"; +import { Random20Hex } from "../../../../utils/misc"; +import { PRIVATEINFO_GetUserCaseInsensitive } from "../../../../utils/user"; import prValidate from "../../../middleware/prudence-validate"; +import { PasswordCompare } from "../../api/v1/auth/auth"; import chartsRouter from "./charts/router"; -import coursesRouter from "./courses/router"; const router: Router = Router({ mergeParams: true }); +/** + * Takes a username and password and returns a unique auth token for the user + * to make ir requests with. + * @name POST /ir/beatoraja/login + */ router.post( - "/auth/login", + "/login", prValidate({ username: "string", password: "string", }), - async (req, res) => {} + async (req, res) => { + const userDoc = await PRIVATEINFO_GetUserCaseInsensitive(req.body.username); + + if (!userDoc) { + return res.status(404).json({ + success: false, + description: `The user ${req.body.username} does not exist.`, + }); + } + + const validPassword = PasswordCompare(req.body.password, userDoc.password); + + if (!validPassword) { + return res.status(401).json({ + success: false, + description: `Invalid password.`, + }); + } + + // User is who they claim to be. + const token = Random20Hex(); + + await db["beatoraja-auth-tokens"].insert({ + userID: userDoc.id, + token, + }); + + return res.status(200).json({ + success: true, + description: `Successfully created auth token.`, + body: { + token, + }, + }); + } ); + + router.use("/charts/:chartSHA256", chartsRouter); -router.use("/courses/:courseSHA256", coursesRouter); export default router; diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index a743fc183..4015f8668 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -37,3 +37,12 @@ export function MStoS(ms: number) { export function RFA(arr: unknown[]) { return arr[Math.floor(Math.random() * arr.length)]; } + +/** + * Splits an Authorization header into the token and the type. + */ +export function SplitAuthorizationHeader(authHeader: string) { + const parts = authHeader.split(" "); + + return { type: parts[0], token: parts.slice(1).join(" ") }; +} diff --git a/server/src/utils/req-ktchi-data.ts b/server/src/utils/req-ktchi-data.ts new file mode 100644 index 000000000..a6c1b02bf --- /dev/null +++ b/server/src/utils/req-ktchi-data.ts @@ -0,0 +1,12 @@ +import { SYMBOL_KtchiData } from "../lib/constants/ktchi"; +import { Request } from "express"; +import { KtchiRequestData } from "./types"; +import deepmerge from "deepmerge"; + +export function AssignToReqKtchiData(req: Request, data: Partial) { + if (!req[SYMBOL_KtchiData]) { + req[SYMBOL_KtchiData] = data; + } else { + req[SYMBOL_KtchiData] = deepmerge(req[SYMBOL_KtchiData]!, data); + } +}