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",
|
"express": "npm:express@^4.21.2",
|
||||||
"ioredis": "npm:ioredis@^5.5.0",
|
"ioredis": "npm:ioredis@^5.5.0",
|
||||||
"validator": "npm:validator@^13.12.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": [],
|
"files": [],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
|||||||
9
deno.lock
generated
9
deno.lock
generated
@@ -20,7 +20,8 @@
|
|||||||
"npm:discord.js@^14.16.3": "14.16.3",
|
"npm:discord.js@^14.16.3": "14.16.3",
|
||||||
"npm:express@^4.21.2": "4.21.2",
|
"npm:express@^4.21.2": "4.21.2",
|
||||||
"npm:ioredis@^5.5.0": "5.5.0",
|
"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": {
|
"jsr": {
|
||||||
"@gz/jwt@0.1.0": {
|
"@gz/jwt@0.1.0": {
|
||||||
@@ -710,6 +711,9 @@
|
|||||||
},
|
},
|
||||||
"ws@8.18.0": {
|
"ws@8.18.0": {
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
||||||
|
},
|
||||||
|
"zod@3.24.2": {
|
||||||
|
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirects": {
|
"redirects": {
|
||||||
@@ -865,7 +869,8 @@
|
|||||||
"npm:discord.js@^14.16.3",
|
"npm:discord.js@^14.16.3",
|
||||||
"npm:express@^4.21.2",
|
"npm:express@^4.21.2",
|
||||||
"npm:ioredis@^5.5.0",
|
"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 { Config } from "./config.ts";
|
||||||
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
|
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
|
||||||
import Profile, { ProfileTokenFormat } from "./data/profiles.ts";
|
import Profile, { ProfileTokenFormat } from "./data/profiles.ts";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
@@ -57,43 +58,40 @@ export function checkQueryTypes<T>(typeDef: T) {
|
|||||||
nxt();
|
nxt();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function checkBodyTypes<T>(typeDef: T) {
|
|
||||||
return (
|
export const validateRequestBody = <T>(schema: z.ZodSchema<T>) => (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
||||||
rq: express.Request,
|
try {
|
||||||
rs: express.Response,
|
schema.parse(rq.body);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nxt();
|
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(
|
export function genericResponseFormat(
|
||||||
failure: boolean,
|
failure: boolean,
|
||||||
msg: string | null = null,
|
msg?: string,
|
||||||
data: object | null = null,
|
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(
|
export function genericResponse(
|
||||||
failure: boolean,
|
failure: boolean,
|
||||||
msg: string | null = null,
|
msg?: string,
|
||||||
data: object | null = null,
|
data?: object,
|
||||||
|
errors?: z.ZodError[],
|
||||||
) {
|
) {
|
||||||
return (_rq: express.Request, rs: express.Response) => {
|
return (_rq: express.Request, rs: express.Response<genericResponse>) => {
|
||||||
rs.json({ failed: failure, message: msg, data: data });
|
rs.json({ failure: failure, errors: errors, message: msg, data: data });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
type RecNetResponse = {
|
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 Logging from "@proxnet/undead-logging";
|
||||||
import Profile from "../profiles.ts";
|
import Profile from "../profiles.ts";
|
||||||
|
import { IntegratedRoomScene } from "../content/roomtypes.ts";
|
||||||
|
|
||||||
const log = new Logging("Instances");
|
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 {
|
export interface RoomInstance {
|
||||||
|
|
||||||
roomInstanceId: number,
|
roomInstanceId: number,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
class PresenceBase {
|
class PresenceBase {
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const userRouter = await import("./routes/user.ts");
|
|||||||
const authRouter = await import("./routes/auth.ts");
|
const authRouter = await import("./routes/auth.ts");
|
||||||
const accountRouter = await import("./routes/account.ts");
|
const accountRouter = await import("./routes/account.ts");
|
||||||
const imgRouter = await import("./routes/img.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(nameserverRouter.route.path, nameserverRouter.route.router);
|
||||||
app.use(apiRouter.route.path, apiRouter.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(authRouter.route.path, authRouter.route.router);
|
||||||
app.use(accountRouter.route.path, accountRouter.route.router);
|
app.use(accountRouter.route.path, accountRouter.route.router);
|
||||||
app.use(imgRouter.route.path, imgRouter.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) => {
|
app.use((rq: express.Request, rs: express.Response) => {
|
||||||
log.e(
|
log.e(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import Profile from "../../data/profiles.ts";
|
import Profile from "../../data/profiles.ts";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/account");
|
export const route = APIUtils.createRouter("/account");
|
||||||
|
|
||||||
@@ -10,6 +11,12 @@ interface CreateAccountRequestBody {
|
|||||||
deviceId: string;
|
deviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CreateAccountRequestBodySchema = z.object({
|
||||||
|
platform: z.string(),
|
||||||
|
platformId: z.string(),
|
||||||
|
deviceId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
const rateLimit = new APIUtils.RateLimiter(25, 5);
|
const rateLimit = new APIUtils.RateLimiter(25, 5);
|
||||||
|
|
||||||
route.router.post("/create",
|
route.router.post("/create",
|
||||||
@@ -17,11 +24,7 @@ route.router.post("/create",
|
|||||||
rateLimit.middle(),
|
rateLimit.middle(),
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.checkBodyTypes<CreateAccountRequestBody>({
|
APIUtils.validateRequestBody(CreateAccountRequestBodySchema),
|
||||||
platform: "",
|
|
||||||
platformId: "",
|
|
||||||
deviceId: "",
|
|
||||||
}),
|
|
||||||
|
|
||||||
async (_rq, rs) => {
|
async (_rq, rs) => {
|
||||||
const newAcc = await Profile.init();
|
const newAcc = await Profile.init();
|
||||||
@@ -33,6 +36,7 @@ route.router.post("/create",
|
|||||||
value: await newAcc.export(),
|
value: await newAcc.export(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
route.router.get("/bulk",
|
route.router.get("/bulk",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { APIUtils, NoBody } from "../../apiutils.ts";
|
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const log = new Logging("PlayerReportingRoute");
|
const log = new Logging("PlayerReportingRoute");
|
||||||
|
|
||||||
@@ -10,11 +11,15 @@ interface HileMessage {
|
|||||||
Message: string;
|
Message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HileMessageSchema = z.object({
|
||||||
|
Message: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
route.router.post('/v1/hile',
|
route.router.post('/v1/hile',
|
||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
express.json(),
|
express.json(),
|
||||||
APIUtils.checkBodyTypes<HileMessage>({Message: ""}),
|
APIUtils.validateRequestBody(HileMessageSchema),
|
||||||
|
|
||||||
(rq: express.Request<NoBody, NoBody, HileMessage>, rs) => {
|
(rq: express.Request<NoBody, NoBody, HileMessage>, rs) => {
|
||||||
rs.sendStatus(204);
|
rs.sendStatus(204);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Profile from "../../data/profiles.ts";
|
|||||||
import { decode } from "@gz/jwt";
|
import { decode } from "@gz/jwt";
|
||||||
import { Config } from "../../config.ts";
|
import { Config } from "../../config.ts";
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
@@ -35,6 +36,35 @@ interface RefreshRequest extends AuthBodyBase {
|
|||||||
|
|
||||||
type TokenRequestBody = TokenRequest | RefreshRequest;
|
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 {
|
interface TokenResponseBody {
|
||||||
error?: string;
|
error?: string;
|
||||||
error_description?: string;
|
error_description?: string;
|
||||||
@@ -47,19 +77,7 @@ route.router.post("/token",
|
|||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.logBody,
|
APIUtils.logBody,
|
||||||
APIUtils.checkBodyTypes<AuthBodyBase>({
|
APIUtils.validateRequestBody<AuthBodyBase>(TokenRequestBodySchema),
|
||||||
grant_type: "",
|
|
||||||
client_id: "",
|
|
||||||
client_secret: "",
|
|
||||||
platform: "",
|
|
||||||
platform_id: "",
|
|
||||||
device_id: "",
|
|
||||||
device_class: "",
|
|
||||||
time: "",
|
|
||||||
ver: "",
|
|
||||||
asid: "",
|
|
||||||
platform_auth: ""
|
|
||||||
}),
|
|
||||||
|
|
||||||
async (
|
async (
|
||||||
rq: express.Request<NoBody, NoBody, TokenRequestBody>,
|
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";
|
import { APIUtils, getSrcIpDefault, NoBody } from "../apiutils.ts";
|
||||||
// @ts-types = "npm:@types/express"
|
// @ts-types = "npm:@types/express"
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { User } from "../data/users.ts";
|
import { User, UserTokenFormat } from "../data/users.ts";
|
||||||
import { Config } from "../config.ts";
|
import { Config } from "../config.ts";
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
|
import { decode } from "@gz/jwt";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
const log = new Logging("UserRoute");
|
const log = new Logging("UserRoute");
|
||||||
|
|
||||||
@@ -25,22 +27,27 @@ interface AuthRequestRoot {
|
|||||||
pubkey: string;
|
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);
|
const rateLimit = new APIUtils.RateLimiter(60, 1);
|
||||||
|
|
||||||
route.router.post(
|
route.router.post("/auth",
|
||||||
"/auth",
|
|
||||||
rateLimit.middle(),
|
rateLimit.middle(),
|
||||||
express.json(),
|
express.json(),
|
||||||
APIUtils.checkBodyTypes<AuthRequestRoot>({
|
APIUtils.validateRequestBody(AuthRequestRootSchema),
|
||||||
client_id: "asdf",
|
|
||||||
message: {
|
|
||||||
timestamp: 0,
|
|
||||||
nonce: "asdf",
|
|
||||||
server_id: "asdf",
|
|
||||||
},
|
|
||||||
signature: "asdf",
|
|
||||||
pubkey: "asdf",
|
|
||||||
}),
|
|
||||||
async (
|
async (
|
||||||
rq: express.Request<NoBody, NoBody, AuthRequestRoot>,
|
rq: express.Request<NoBody, NoBody, AuthRequestRoot>,
|
||||||
rs: express.Response,
|
rs: express.Response,
|
||||||
@@ -101,6 +108,7 @@ route.router.post(
|
|||||||
pubkey: rq.body.pubkey,
|
pubkey: rq.body.pubkey,
|
||||||
});
|
});
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
|
log.w(`Obj null`);
|
||||||
rs.sendStatus(500);
|
rs.sendStatus(500);
|
||||||
return;
|
return;
|
||||||
} else user = obj;
|
} else user = obj;
|
||||||
@@ -118,3 +126,22 @@ route.router.post(
|
|||||||
rs.json({ token: token });
|
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