This commit is contained in:
2025-09-11 19:47:33 -04:00
parent 317da3aaf7
commit c2eb111291
14 changed files with 217 additions and 44 deletions

View File

@@ -1,11 +1,12 @@
import { createHonoRoute } from "../../../util/import.ts";
import { authenticate, galvanicError, GalvanicErrors, RateLimiter, recNetError, statusResponse } from "../../../util/api.ts";
import { RateLimiter, recNetResultResponse, statusResponse } from "../../../util/api.ts";
import Server from "../../../server/server.ts";
import z from "zod";
import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts";
import { PlatformType } from "../../../server/platforms/types.ts";
import Steam from "../../../util/steam/steam.ts";
import { HTTPStatus } from "@oneday/http-status";
import { accountRoute } from "./me/root.ts";
export const route = createHonoRoute('/account');
@@ -35,20 +36,22 @@ const createAccountBodySchema = z.object({
});
route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form', createAccountBodySchema), async c => {
if (c.req.header("User-Agent") !== "BestHTTP") return recNetResultResponse(c, HTTPStatus.OK, false, "Platform error");
const form = c.req.valid('form');
if (typeof form.platform == 'undefined')
return c.json(galvanicError(GalvanicErrors.jex));
if (form.platform == null)
return recNetResultResponse(c, HTTPStatus.OK, false, "Platform error");
else if (form.platform == PlatformType.Steam) {
const steam = await Steam.GetPlayerSummaries([form.platformId]);
if (steam.length == 0)
return c.json(galvanicError(GalvanicErrors.sploot));
return recNetResultResponse(c, HTTPStatus.OK, false, "Steam profile could not be fetched");
const cachedlogins = await Server.Platforms.getCachedLogins(form.platform, form.platformId, true);
if (cachedlogins.length == 0) {
const profile = await Server.Profiles.create(form.platform, form.platformId, steam[0].realname ?? steam[0].personaname);
if (!profile) return c.json(galvanicError(GalvanicErrors.sploot));
if (!profile) return recNetResultResponse(c, HTTPStatus.OK, false, "Account could not be created");
Server.Content.steamAvatarDownloadForProfile(profile, steam[0].avatarfull);
@@ -60,7 +63,7 @@ route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form'
} else {
const profile = await Server.Profiles.create(form.platform, form.platformId);
if (!profile) return c.json(galvanicError(GalvanicErrors.sploot));
if (!profile) return recNetResultResponse(c, HTTPStatus.OK, false, "Account could not be created");
return c.json({
success: true,
@@ -68,14 +71,11 @@ route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form'
});
}
} else return c.json(recNetError("Not a Steam user"));
} else return recNetResultResponse(c, HTTPStatus.OK, false, "Not a Steam user");
});
route.app.get('/me', authenticate, c => {
const profile = c.get('profile');
return c.json(profile.selfExport());
});
route.app.route('/', accountRoute.app);
const getAccountByIdParamSchema = z.object({
id: z.coerce.number().max(Math.pow(2, 31))

View File

@@ -0,0 +1,38 @@
import { HTTPStatus } from "@oneday/http-status";
import { authenticate, recNetResultResponse } from "../../../../util/api.ts";
import { createHonoRoute } from "../../../../util/import.ts";
import z from "zod";
import { typedZValidator } from "../../../../util/validators.ts";
import Server from "../../../../server/server.ts";
export const accountRoute = createHonoRoute("/");
accountRoute.app.get('/me', authenticate, c => {
const profile = c.get('profile');
return c.json(profile.selfExport());
});
const changeUsernameFormSchema = z.object({
username: z.string().min(4).max(48).regex(/^[A-Za-z0-9._-]+$/)
});
accountRoute.app.put('/me/username',
authenticate,
typedZValidator('form', changeUsernameFormSchema, true, "Username must only contain letters, numbers, and any of these symbols: ._-"),
async c => {
const newUsername = c.req.valid('form').username;
const takenProf = await Server.Profiles.getByUsername(newUsername);
if (takenProf) return recNetResultResponse(c, HTTPStatus.OK, false, "Username taken");
else {
await c.get('profile').setUsername(newUsername);
return recNetResultResponse(c, HTTPStatus.OK, true, "Username updated successfully");
}
}
);
// birthday: 1970-09-10T00:00:00.0000000Z (based on age entered in OOBE)
accountRoute.app.put('/me/birthday', authenticate, c => {
return recNetResultResponse(c, HTTPStatus.OK, true, "Stub. Birthdays are not saved in Galvanic Corrosion.");
});

View File

@@ -1,6 +1,7 @@
import { HTTPStatus } from "@oneday/http-status";
import { profileAvatarSchema } from "../../../server/profiles/content/Avatar.ts";
import Server from "../../../server/server.ts";
import { authenticate } from "../../../util/api.ts";
import { authenticate, statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts";
@@ -23,7 +24,7 @@ route.app.get('/v2', async c => {
route.app.post('/v2/set', typedZValidator('json', profileAvatarSchema), async c => {
const outfit = c.req.valid('json');
await c.get('profile').Avatar.setAvatar(outfit);
return c.status(200);
return statusResponse(c, HTTPStatus.OK);
});
route.app.get('/v3/saved', c => {

View File

@@ -1,5 +1,7 @@
import z from "zod";
import Server from "../../../server/server.ts";
import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts";
export const route = createHonoRoute("/config");
@@ -7,6 +9,23 @@ route.app.get('/v1/amplitude', c => {
return c.json({AmplitudeKey: ""});
});
function createNuxConfig(n: number) {
return {
Version: 0,
ButtonNumber: n,
Override: 0,
DefaultRoomName: "DormRoom",
DefaultTitle: "Dorm Room"
}
}
// i don't know what id is right now. it is an arbitrary number given by the game.
const cohortNuxIdParamSchema = z.object({
id: z.coerce.number().nonnegative().max(32)
});
route.app.get('/v1/cohortnux/:id', typedZValidator('param', cohortNuxIdParamSchema), c => {
return c.json([0, 1, 2, 3].map(n => createNuxConfig(n)));
});
route.app.get('/v2', c => {
return c.json(Server.getPublicConfig());
});

View File

@@ -33,4 +33,10 @@ route.app.get('/v2/myrooms', authenticate, async c => {
const rooms = exs.map(ex => ex.Room);
return c.json(rooms);
});
route.app.get('/v1/hot', authenticate, async c => {
const agRoomIds = Server.Rooms.getAgRoomIds();
const factories = await Server.Rooms.getMany(...agRoomIds.values().toArray());
return c.json((await Promise.all(factories.map(factory => factory.export()))).map(roomDetails => roomDetails.Room));
});

View File

@@ -0,0 +1,32 @@
import z from "zod";
import { createHonoRoute } from "../../../util/import.ts";
import { authenticate, recNetResultResponse } from "../../../util/api.ts";
import { typedZValidator } from "../../../util/validators.ts";
import { HTTPStatus } from "@oneday/http-status";
export const route = createHonoRoute('/account');
const changePasswordFormSchema = z.object({
/**
* Password requirements:
* - Lowercase character
* - Uppercase character
* - A digit
* - A special character
* - 8 characters minimum
*/
newPassword: z.string().max(64).regex(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/)
});
route.app.post('/me/changepassword',
authenticate,
typedZValidator('form', changePasswordFormSchema, true,
"Password must contain a lowercase and uppercase character, a digit, a special character, and be at least 8 characters in length"
),
async c => {
await c.get('profile').setPassword(c.req.valid('form').newPassword);
return recNetResultResponse(c, HTTPStatus.OK, true, "Password updated successfully");
}
);

View File

@@ -22,7 +22,7 @@ const authBodyBaseSchema = z.object({
}),
platform_id: z.string().min(4),
device_id: z.string().min(4),
device_class: z.string().transform(transformCheckEnum<DeviceClass>(DeviceClass)),
device_class: z.coerce.number().transform(transformCheckEnum<DeviceClass>(DeviceClass)),
time: z.coerce.date(),
ver: z.literal(gameVerString),
asid: z.coerce.number(),
@@ -97,12 +97,9 @@ route.app.post('/token', typedZValidator('form', tokenGrantSchema), async c => {
const profile = await Server.Profiles.get(token.sub);
if (!profile) return error(TokenRequestError.AccessDenied);
const accessToken = await Server.Platforms.getToken(profile, TokenType.Access);
const refreshToken = await Server.Platforms.getToken(profile, TokenType.Refresh);
return c.json({
access_token: accessToken,
refresh_token: refreshToken,
token: await Server.Platforms.getToken(profile, TokenType.Access),
});
} catch (err) {
log.w(`Authentication error (token req): ${(err as Error).stack}`);