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

@@ -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
View File

@@ -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"
] ]
} }
} }

View File

@@ -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 = {

View File

@@ -0,0 +1,8 @@
class RoomsBase {
}
const Rooms = new RoomsBase();
export default Rooms;

View 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[]
}

View File

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

View File

@@ -1,3 +1,5 @@
class PresenceBase { class PresenceBase {

View File

@@ -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(

View File

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

View File

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

View File

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