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:
2025-03-25 21:54:08 -04:00
parent de3d653446
commit 463e3ef71b
14 changed files with 287 additions and 110 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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
View 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);

View 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);
},
)

View File

@@ -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);
}
});