Replace legacy checkBodyType with Zod
Start matchmaking integration Start rooms API Move existing room scene locations to roomtypes file Auth checkExpired util for client refreshing
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
"express": "npm:express@^4.21.2",
|
||||
"ioredis": "npm:ioredis@^5.5.0",
|
||||
"validator": "npm:validator@^13.12.0",
|
||||
"bcrypt": "https://deno.land/x/bcrypt@v0.3.0/mod.ts"
|
||||
"zod": "npm:zod@^3.24.2"
|
||||
},
|
||||
"files": [],
|
||||
"compilerOptions": {
|
||||
|
||||
9
deno.lock
generated
9
deno.lock
generated
@@ -20,7 +20,8 @@
|
||||
"npm:discord.js@^14.16.3": "14.16.3",
|
||||
"npm:express@^4.21.2": "4.21.2",
|
||||
"npm:ioredis@^5.5.0": "5.5.0",
|
||||
"npm:validator@^13.12.0": "13.12.0"
|
||||
"npm:validator@^13.12.0": "13.12.0",
|
||||
"npm:zod@^3.24.2": "3.24.2"
|
||||
},
|
||||
"jsr": {
|
||||
"@gz/jwt@0.1.0": {
|
||||
@@ -710,6 +711,9 @@
|
||||
},
|
||||
"ws@8.18.0": {
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
||||
},
|
||||
"zod@3.24.2": {
|
||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="
|
||||
}
|
||||
},
|
||||
"redirects": {
|
||||
@@ -865,7 +869,8 @@
|
||||
"npm:discord.js@^14.16.3",
|
||||
"npm:express@^4.21.2",
|
||||
"npm:ioredis@^5.5.0",
|
||||
"npm:validator@^13.12.0"
|
||||
"npm:validator@^13.12.0",
|
||||
"npm:zod@^3.24.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { decode } from "@gz/jwt";
|
||||
import { Config } from "./config.ts";
|
||||
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
|
||||
import Profile, { ProfileTokenFormat } from "./data/profiles.ts";
|
||||
import z from "zod";
|
||||
|
||||
const config = Config.getConfig();
|
||||
|
||||
@@ -57,43 +58,40 @@ export function checkQueryTypes<T>(typeDef: T) {
|
||||
nxt();
|
||||
};
|
||||
}
|
||||
export function checkBodyTypes<T>(typeDef: T) {
|
||||
return (
|
||||
rq: express.Request,
|
||||
rs: express.Response,
|
||||
nxt: express.NextFunction,
|
||||
) => {
|
||||
for (const key in typeDef) {
|
||||
if (typeof rq.body[key] !== typeof typeDef[key]) {
|
||||
log.e(`Body check for key '${key}' failed.`);
|
||||
rs.statusCode = 400;
|
||||
rs.json(
|
||||
genericResponseFormat(
|
||||
true,
|
||||
"One or more body values were invalid or not found.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export const validateRequestBody = <T>(schema: z.ZodSchema<T>) => (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
||||
try {
|
||||
schema.parse(rq.body);
|
||||
nxt();
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError)
|
||||
rs.status(400).json(genericResponseFormat(true, "Bad request", undefined, error.errors));
|
||||
}
|
||||
};
|
||||
|
||||
type genericResponse = {
|
||||
failure: boolean,
|
||||
errors?: object, // zod only
|
||||
message?: string,
|
||||
data?: object
|
||||
}
|
||||
|
||||
export function genericResponseFormat(
|
||||
failure: boolean,
|
||||
msg: string | null = null,
|
||||
data: object | null = null,
|
||||
msg?: string,
|
||||
data?: object,
|
||||
errors?: object,
|
||||
) {
|
||||
return { failed: failure, message: msg, data: data };
|
||||
return { failure: failure, errors: errors, message: msg, data: data } as genericResponse;
|
||||
}
|
||||
export function genericResponse(
|
||||
failure: boolean,
|
||||
msg: string | null = null,
|
||||
data: object | null = null,
|
||||
msg?: string,
|
||||
data?: object,
|
||||
errors?: z.ZodError[],
|
||||
) {
|
||||
return (_rq: express.Request, rs: express.Response) => {
|
||||
rs.json({ failed: failure, message: msg, data: data });
|
||||
return (_rq: express.Request, rs: express.Response<genericResponse>) => {
|
||||
rs.json({ failure: failure, errors: errors, message: msg, data: data });
|
||||
};
|
||||
}
|
||||
type RecNetResponse = {
|
||||
|
||||
8
src/data/content/rooms.ts
Normal file
8
src/data/content/rooms.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
class RoomsBase {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const Rooms = new RoomsBase();
|
||||
export default Rooms;
|
||||
123
src/data/content/roomtypes.ts
Normal file
123
src/data/content/roomtypes.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
export enum IntegratedRoomScene {
|
||||
Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04",
|
||||
DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
||||
RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
||||
ThreeDCharades = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
||||
DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||
DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||
Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499",
|
||||
Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
Paintball_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
Paintball_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
Paintball_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
PaintballVR_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
PaintballVR_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
PaintballVR_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
PaintballVR_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
PaintballVR_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
||||
TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
||||
CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045",
|
||||
IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
||||
Soccer = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||
LaserTagHangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||
LaserTagCyberJunk = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||
RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||
RecRoyaleVR = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||
RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1",
|
||||
Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
||||
PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346",
|
||||
MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
||||
Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
||||
ArtTesting = "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79",
|
||||
River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||
PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||
Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||
Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||
CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||
DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
}
|
||||
|
||||
export enum RoomState {
|
||||
Active,
|
||||
PendingJunior = 11,
|
||||
Moderation_PendingReview = 100,
|
||||
Moderation_Closed,
|
||||
MarkedForDelete = 1000
|
||||
}
|
||||
|
||||
export enum RoomAccessibility {
|
||||
Private,
|
||||
Public,
|
||||
Unlisted
|
||||
}
|
||||
|
||||
export interface Room {
|
||||
RoomId: number,
|
||||
Name: string,
|
||||
Description: string,
|
||||
CreatorPlayerId: number,
|
||||
ImageName: string,
|
||||
State: RoomState,
|
||||
Accessibility: RoomAccessibility,
|
||||
SupportsLevelVoting: boolean,
|
||||
IsAGRoom: boolean,
|
||||
IsDormRoom?: boolean,
|
||||
CloningAllowed: boolean,
|
||||
SupportsVRLow?: boolean,
|
||||
SupportsMobile?: boolean,
|
||||
SupportsScreens: boolean,
|
||||
SupportsWalkVR: boolean,
|
||||
SupportsTeleportVR: boolean,
|
||||
AllowsJuniors: boolean,
|
||||
RoomWarningMask: number, // generated by dedicated mask generation function
|
||||
CustomRoomWarning: string,
|
||||
DisableMicAutoMute?: boolean
|
||||
}
|
||||
|
||||
export enum TagType {
|
||||
General,
|
||||
Auto,
|
||||
AGOnly,
|
||||
Banned
|
||||
}
|
||||
|
||||
export interface TagDTO {
|
||||
Tag: string,
|
||||
Type: TagType
|
||||
}
|
||||
|
||||
export interface RoomScene {
|
||||
RoomSceneId: number,
|
||||
RoomId: number,
|
||||
RoomSceneLocationId: IntegratedRoomScene,
|
||||
Name: string,
|
||||
IsSandbox: boolean,
|
||||
DataBlobName: string,
|
||||
MaxPlayers: number,
|
||||
CanMatchmakeInto?: boolean,
|
||||
DataModifiedAt: string
|
||||
}
|
||||
|
||||
export interface RoomDetails {
|
||||
Room: Room,
|
||||
Scenes: RoomScene,
|
||||
CoOwners: number[],
|
||||
InvitedCoOwners: number[],
|
||||
Moderators?: number[],
|
||||
InvitedModerators?: number[],
|
||||
Hosts: number[],
|
||||
InvitedHosts: number[],
|
||||
CheerCount: number,
|
||||
FavoriteCount: number,
|
||||
VisitCount: number,
|
||||
Tags: TagDTO[]
|
||||
}
|
||||
@@ -1,56 +1,9 @@
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import Profile from "../profiles.ts";
|
||||
import { IntegratedRoomScene } from "../content/roomtypes.ts";
|
||||
|
||||
const log = new Logging("Instances");
|
||||
|
||||
enum IntegratedRoomScene {
|
||||
Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04",
|
||||
DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
||||
RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
||||
ThreeDCharades = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
||||
DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||
DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||
Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499",
|
||||
Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
Paintball_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
Paintball_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
Paintball_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
PaintballVR_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
PaintballVR_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
PaintballVR_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
PaintballVR_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
PaintballVR_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
||||
TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
||||
CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045",
|
||||
IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
||||
Soccer = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||
LaserTagHangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||
LaserTagCyberJunk = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||
RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||
RecRoyaleVR = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||
RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1",
|
||||
Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
||||
PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346",
|
||||
MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
||||
Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
||||
ArtTesting = "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79",
|
||||
River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||
Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||
Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||
Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||
Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||
Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||
PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||
Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||
Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||
CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||
DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||
}
|
||||
|
||||
export interface RoomInstance {
|
||||
|
||||
roomInstanceId: number,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
class PresenceBase {
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ const userRouter = await import("./routes/user.ts");
|
||||
const authRouter = await import("./routes/auth.ts");
|
||||
const accountRouter = await import("./routes/account.ts");
|
||||
const imgRouter = await import("./routes/img.ts");
|
||||
const matchRouter = await import("./routes/match.ts");
|
||||
|
||||
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
|
||||
app.use(apiRouter.route.path, apiRouter.route.router);
|
||||
@@ -83,6 +84,7 @@ app.use(userRouter.route.path, userRouter.route.router);
|
||||
app.use(authRouter.route.path, authRouter.route.router);
|
||||
app.use(accountRouter.route.path, accountRouter.route.router);
|
||||
app.use(imgRouter.route.path, imgRouter.route.router);
|
||||
app.use(matchRouter.route.path, matchRouter.route.router);
|
||||
|
||||
app.use((rq: express.Request, rs: express.Response) => {
|
||||
log.e(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
import express from "express";
|
||||
import Profile from "../../data/profiles.ts";
|
||||
import { z } from "zod";
|
||||
|
||||
export const route = APIUtils.createRouter("/account");
|
||||
|
||||
@@ -10,6 +11,12 @@ interface CreateAccountRequestBody {
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
const CreateAccountRequestBodySchema = z.object({
|
||||
platform: z.string(),
|
||||
platformId: z.string(),
|
||||
deviceId: z.string()
|
||||
});
|
||||
|
||||
const rateLimit = new APIUtils.RateLimiter(25, 5);
|
||||
|
||||
route.router.post("/create",
|
||||
@@ -17,11 +24,7 @@ route.router.post("/create",
|
||||
rateLimit.middle(),
|
||||
APIUtils.Authentication,
|
||||
express.urlencoded({ extended: true }),
|
||||
APIUtils.checkBodyTypes<CreateAccountRequestBody>({
|
||||
platform: "",
|
||||
platformId: "",
|
||||
deviceId: "",
|
||||
}),
|
||||
APIUtils.validateRequestBody(CreateAccountRequestBodySchema),
|
||||
|
||||
async (_rq, rs) => {
|
||||
const newAcc = await Profile.init();
|
||||
@@ -33,6 +36,7 @@ route.router.post("/create",
|
||||
value: await newAcc.export(),
|
||||
});
|
||||
},
|
||||
|
||||
);
|
||||
|
||||
route.router.get("/bulk",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||
import express from "express";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { z } from "zod";
|
||||
|
||||
const log = new Logging("PlayerReportingRoute");
|
||||
|
||||
@@ -10,11 +11,15 @@ interface HileMessage {
|
||||
Message: string;
|
||||
}
|
||||
|
||||
const HileMessageSchema = z.object({
|
||||
Message: z.string()
|
||||
});
|
||||
|
||||
route.router.post('/v1/hile',
|
||||
|
||||
APIUtils.Authentication,
|
||||
express.json(),
|
||||
APIUtils.checkBodyTypes<HileMessage>({Message: ""}),
|
||||
APIUtils.validateRequestBody(HileMessageSchema),
|
||||
|
||||
(rq: express.Request<NoBody, NoBody, HileMessage>, rs) => {
|
||||
rs.sendStatus(204);
|
||||
|
||||
@@ -4,6 +4,7 @@ import Profile from "../../data/profiles.ts";
|
||||
import { decode } from "@gz/jwt";
|
||||
import { Config } from "../../config.ts";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { z } from "zod";
|
||||
|
||||
const config = Config.getConfig();
|
||||
|
||||
@@ -35,6 +36,35 @@ interface RefreshRequest extends AuthBodyBase {
|
||||
|
||||
type TokenRequestBody = TokenRequest | RefreshRequest;
|
||||
|
||||
const AuthBodyBaseSchema = z.object({
|
||||
grant_type: z.string(),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
platform: z.string(),
|
||||
platform_id: z.string(),
|
||||
device_id: z.string(),
|
||||
device_class: z.string(),
|
||||
time: z.string(),
|
||||
ver: z.string(),
|
||||
asid: z.string(),
|
||||
platform_auth: z.string(),
|
||||
});
|
||||
|
||||
const TokenRequestSchema = AuthBodyBaseSchema.extend({
|
||||
grant_type: z.literal('cached_login'),
|
||||
account_id: z.string(),
|
||||
});
|
||||
|
||||
const RefreshRequestSchema = AuthBodyBaseSchema.extend({
|
||||
grant_type: z.literal('refresh_token'),
|
||||
refresh_token: z.string(),
|
||||
});
|
||||
|
||||
const TokenRequestBodySchema = z.discriminatedUnion('grant_type', [
|
||||
TokenRequestSchema,
|
||||
RefreshRequestSchema,
|
||||
]);
|
||||
|
||||
interface TokenResponseBody {
|
||||
error?: string;
|
||||
error_description?: string;
|
||||
@@ -47,19 +77,7 @@ route.router.post("/token",
|
||||
APIUtils.Authentication,
|
||||
express.urlencoded({ extended: true }),
|
||||
APIUtils.logBody,
|
||||
APIUtils.checkBodyTypes<AuthBodyBase>({
|
||||
grant_type: "",
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
platform: "",
|
||||
platform_id: "",
|
||||
device_id: "",
|
||||
device_class: "",
|
||||
time: "",
|
||||
ver: "",
|
||||
asid: "",
|
||||
platform_auth: ""
|
||||
}),
|
||||
APIUtils.validateRequestBody<AuthBodyBase>(TokenRequestBodySchema),
|
||||
|
||||
async (
|
||||
rq: express.Request<NoBody, NoBody, TokenRequestBody>,
|
||||
|
||||
6
src/routes/match.ts
Normal file
6
src/routes/match.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { APIUtils } from "../apiutils.ts";
|
||||
import { route as PlayerRoute } from "./match/player.ts";
|
||||
|
||||
export const route = APIUtils.createRouter('/match');
|
||||
|
||||
route.router.use(PlayerRoute.path, PlayerRoute.router);
|
||||
26
src/routes/match/player.ts
Normal file
26
src/routes/match/player.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
import express from "express";
|
||||
|
||||
export const route = APIUtils.createRouter('/player');
|
||||
|
||||
interface BaseLoginLock {
|
||||
LoginLock: string
|
||||
}
|
||||
|
||||
const LoginSchema = z.object({
|
||||
LoginLock: z.string().uuid("LoginLock must be a UUIDv4")
|
||||
});
|
||||
|
||||
route.router.post('/login',
|
||||
|
||||
APIUtils.Authentication,
|
||||
express.urlencoded({extended: true}),
|
||||
APIUtils.validateRequestBody(LoginSchema),
|
||||
|
||||
(rq, rs) => {
|
||||
// temporary
|
||||
rs.sendStatus(200);
|
||||
},
|
||||
|
||||
)
|
||||
@@ -1,10 +1,12 @@
|
||||
import { APIUtils, getSrcIpDefault, NoBody } from "../apiutils.ts";
|
||||
// @ts-types = "npm:@types/express"
|
||||
import express from "express";
|
||||
import { User } from "../data/users.ts";
|
||||
import { User, UserTokenFormat } from "../data/users.ts";
|
||||
import { Config } from "../config.ts";
|
||||
import crypto from "node:crypto";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import { decode } from "@gz/jwt";
|
||||
import z from "zod";
|
||||
|
||||
const log = new Logging("UserRoute");
|
||||
|
||||
@@ -25,22 +27,27 @@ interface AuthRequestRoot {
|
||||
pubkey: string;
|
||||
}
|
||||
|
||||
const AuthRequestSecSchema = z.object({
|
||||
timestamp: z.number(),
|
||||
nonce: z.string(),
|
||||
server_id: z.string(),
|
||||
});
|
||||
|
||||
const AuthRequestRootSchema = z.object({
|
||||
client_id: z.string(),
|
||||
message: AuthRequestSecSchema,
|
||||
signature: z.string(),
|
||||
pubkey: z.string(),
|
||||
});
|
||||
|
||||
const rateLimit = new APIUtils.RateLimiter(60, 1);
|
||||
|
||||
route.router.post(
|
||||
"/auth",
|
||||
route.router.post("/auth",
|
||||
|
||||
rateLimit.middle(),
|
||||
express.json(),
|
||||
APIUtils.checkBodyTypes<AuthRequestRoot>({
|
||||
client_id: "asdf",
|
||||
message: {
|
||||
timestamp: 0,
|
||||
nonce: "asdf",
|
||||
server_id: "asdf",
|
||||
},
|
||||
signature: "asdf",
|
||||
pubkey: "asdf",
|
||||
}),
|
||||
APIUtils.validateRequestBody(AuthRequestRootSchema),
|
||||
|
||||
async (
|
||||
rq: express.Request<NoBody, NoBody, AuthRequestRoot>,
|
||||
rs: express.Response,
|
||||
@@ -101,6 +108,7 @@ route.router.post(
|
||||
pubkey: rq.body.pubkey,
|
||||
});
|
||||
if (obj == null) {
|
||||
log.w(`Obj null`);
|
||||
rs.sendStatus(500);
|
||||
return;
|
||||
} else user = obj;
|
||||
@@ -118,3 +126,22 @@ route.router.post(
|
||||
rs.json({ token: token });
|
||||
},
|
||||
);
|
||||
|
||||
const checkRateLimit = new APIUtils.RateLimiter(10, 3);
|
||||
|
||||
route.router.get('/checkExpired', checkRateLimit.middle(), async (rq, rs) => {
|
||||
|
||||
const token = rq.header('GalvanicAuth');
|
||||
if (!token) {
|
||||
rs.json(true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedToken = await decode<UserTokenFormat>(token, config.auth.secret, { algorithm: "HS512", leeway: 31536000 }); // 1 year leeway
|
||||
rs.json(decodedToken.exp < Math.round(Date.now() / 1000));
|
||||
} catch {
|
||||
rs.json(true);
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user