Further login process

* APIUtils addition: query validation
* Coach and Server accounts are now properly created if they do not exist
* Profiles now cannot be IDs 1 or 2 (reservedIds)
* Fixed profile username exists bug
* Added relationship manager
* Started relationship management
* DeviceClass and VRMovementMode enum defaults for reserved profiles
* Presence update simplification
* Progression fixes
* Relationship query and object fixes
* Base configuration is now rate limited
* Progression route no longer requires authentication, instead is rate limited
* Base relationships with reserved profiles (Coach and Server)
* DeviceClass required for login
* Get presence route
* Socket route no longer logs
* Socket target base finished
This commit is contained in:
2025-03-30 19:29:57 -04:00
parent 026f9c8bd8
commit 639e809a20
19 changed files with 270 additions and 81 deletions

View File

@@ -28,6 +28,12 @@ route.router.post("/create",
async (_rq, rs) => {
const newAcc = await Profile.init();
if (newAcc == null) {
rs.json({
success: false
});
return;
}
rs.locals.user.addAssociatedProfile(newAcc.getId());

View File

@@ -3,7 +3,9 @@ import { GameConfigs } from "../../data/config.ts";
export const route = APIUtils.createRouter("/config");
route.router.get("/v2", (_rq, rs) => {
const rateLimit = new APIUtils.RateLimiter(60, 2);
route.router.get("/v2", rateLimit.middle(), (_rq, rs) => {
const config = GameConfigs.getConfig();
if (config == null) rs.sendStatus(500);
else rs.json(config);
@@ -11,6 +13,7 @@ route.router.get("/v2", (_rq, rs) => {
route.router.get('/v1/amplitude',
APIUtils.setCacheAllowed,
rateLimit.middle(),
(_rq, rs) => {
rs.json({AmplitudeKey: ""});
}

View File

@@ -10,7 +10,7 @@ route.router.get('/v1/:id',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
(rq: express.Request<{ id: string }>, rs) => {
async (rq: express.Request<{ id: string }>, rs) => {
const unparsedPlayerId = rq.params.id;
const parsedPlayerId = parseInt(unparsedPlayerId);
if (isNaN(parsedPlayerId)) {
@@ -18,7 +18,7 @@ route.router.get('/v1/:id',
return;
}
rs.json(UnifiedProfile.get(parsedPlayerId).Reputation.getReputation());
rs.json(await UnifiedProfile.get(parsedPlayerId).Reputation.getReputation());
}
);

View File

@@ -5,11 +5,13 @@ import UnifiedProfile from "../../data/profiles.ts";
const log = new Logging("ProgressionRoute");
const rateLimit = new APIUtils.RateLimiter(60, 2);
export const route = APIUtils.createRouter("/players");
route.router.get('/v1/progression/:id',
APIUtils.Authentication,
rateLimit.middle(),
async (rq: express.Request<{ id: string }>, rs) => {
const unparsedPlayerId = rq.params.id;

View File

@@ -9,7 +9,7 @@ route.router.get('/v2/get',
APIUtils.AuthenticationType(AuthType.Game),
(_rq, rs) => {
rs.json([]); // temporary
rs.json(rs.locals.profile.Relationships.getRelationships());
}
);

View File

@@ -98,6 +98,7 @@ route.router.post("/token",
rq.body.platform === "0",
rq.body.ver === '20191120',
rq.body.device_class.length === 1,
!isNaN(Number(rq.body.device_class)),
!(rq.body.device_id.length > 96),
!(rq.body.client_secret.length > 96),
!(rq.body.platform_id.length > 32),
@@ -110,9 +111,8 @@ route.router.post("/token",
return;
}
const accounts = await rs.locals.user.getAssociatedProfiles();
let targetAccount: number;
if (rq.body.grant_type == 'cached_login') targetAccount = parseInt(rq.body.account_id);
else {
const refreshToken = rq.body.refresh_token;
@@ -128,15 +128,17 @@ route.router.post("/token",
requestFailed();
return;
}
targetAccount = parseInt(decodedToken.sub ? decodedToken.sub : "NaN");
}
if (isNaN(targetAccount)) {
requestFailed();
return;
}
const accounts = await rs.locals.user.getAssociatedProfiles();
if (!accounts.has(targetAccount)) {
requestFailed("access_denied");
return;
@@ -160,6 +162,6 @@ route.router.post("/token",
refresh_token: token,
});
await profile.setKnownDeviceClass(rq.body.device_class);
await profile.setKnownDeviceClass(Number(rq.body.device_class));
},
);

View File

@@ -2,9 +2,10 @@ import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
import express from "express";
import Matchmaking from "../../data/live/base.ts";
import Presence from "../../data/live/presence.ts";
import Presence, { PresenceExport } from "../../data/live/presence.ts";
import { AuthType } from "../../data/users.ts";
import Logging from "@proxnet/undead-logging";
import UnifiedProfile from "../../data/profiles.ts";
const log = new Logging("MatchPlayerRoute");
@@ -18,6 +19,31 @@ const LoginSchema = z.object({
LoginLock: z.string().uuid("LoginLock must be a UUIDv4")
});
route.router.get('/',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
APIUtils.validateQuery(z.object({ id: z.union([z.string(), z.array(z.string())]) })),
async (rq: express.Request<NoBody, PresenceExport[], NoBody, { id: string[] | string }>, rs) => {
let ids: number[] = [];
if (typeof rq.query.id == 'object') ids = rq.query.id.map(val => parseInt(val));
else ids.push(parseInt(rq.query.id));
ids = ids.filter(val => !isNaN(val));
const presExport: PresenceExport[] = [];
for (const id of ids) {
const pres = await Presence.get(UnifiedProfile.get(id));
await pres.update();
presExport.push(await pres.export());
}
rs.json(presExport);
log.d(JSON.stringify(presExport));
}
)
route.router.post('/login',
APIUtils.Authentication,
@@ -40,8 +66,9 @@ route.router.post('/logout',
express.urlencoded({extended: true}),
APIUtils.validateRequestBody(LoginSchema),
(rq, rs) => {
(_rq, rs) => {
Matchmaking.deleteLoginLock(rs.locals.profile);
rs.sendStatus(200);
}
)
@@ -56,6 +83,7 @@ route.router.post('/heartbeat',
async (_rq, rs) => {
const pres = await Presence.get(rs.locals.profile);
await pres.update();
log.d(`pres heartbeat for ${rs.locals.profile.getId()}: ${JSON.stringify(await pres.export())}`);
rs.json(await pres.export());
}