various fixes
Some checks failed
Galvanic Corrosion Cross-Compile / build (push) Failing after 39s

* Reverted shutdown mechanism
* Socket authentication fix for Cloudflare users
    - Cloudflare formats headers
* Steam auth verbose
* Upload artifacts to CDN and send Discord webhook link in #dev
This commit is contained in:
2025-05-12 20:57:52 -04:00
parent 83440a9245
commit 9ce5431d9d
12 changed files with 98 additions and 144 deletions

View File

@@ -37,7 +37,7 @@ try {
const data = Deno.readTextFileSync(`${RootPath}/res/words.json`);
charades = JSON.parse(data);
} catch (err) {
log.e(`Could not read charades words config from disk!`);
log.e(`Could not read charades words config from disk: ${(err as Error).message}`);
}
export function getWords() {

View File

@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
enum AvatarItemType {
enum _AvatarItemType {
None = -1,
Hat,
BackHead,

View File

@@ -49,18 +49,29 @@ export async function AuthenticateUserTicket(ticket: string, userid: string) {
params.append('appid', "471710");
params.append('ticket', ticket);
const res = await fetch(`https://api.steampowered.com/ISteamUserAuth/AuthenticateUserTicket/v1?${params}`);
const resjson = (await res.json()) as SteamRes;
if (resjson.response.error) {
log.w(`Steam Authentication failed: (${resjson.response.error.errorcode}) ${resjson.response.error.errordesc}`);
return false;
}
log.d(JSON.stringify(resjson.response));
if (resjson.response.params) return resjson.response.params.steamid === userid && resjson.response.params.ownersteamid === userid;
else {
log.w("Steam Authentication failed: Steam response did not contain params or error! This should never be logged!");
try {
const res = await fetch(`https://api.steampowered.com/ISteamUserAuth/AuthenticateUserTicket/v1?${params}`);
const resjson = (await res.json()) as SteamRes;
if (resjson.response.error) {
log.w(`Steam Authentication failed: (${resjson.response.error.errorcode}) ${resjson.response.error.errordesc}`);
// add more error codes later if needed
const conditions = [
resjson.response.error.errorcode == 100
].includes(true);
if (conditions) log.w('This error indicates a client problem.');
return false;
}
log.d(JSON.stringify(resjson.response));
if (resjson.response.params) return resjson.response.params.steamid === userid && resjson.response.params.ownersteamid === userid;
else {
log.w("Steam Authentication failed: Steam response did not contain params or error! This should never be logged!");
return false;
}
} catch (err) {
log.w(`Steam Authentication failed: ${(err as Error).message}`);
return false;
}
}

View File

@@ -130,20 +130,32 @@ try {
valid: false
}
type AuthResult = FailedAuth | SuccessfulAuth;
// Please rewrite this for the love of God
const authenticate = async (req: Request) => {
const authHeader = req.headers.get('authorization');
if (!authHeader) return { valid: false } as AuthResult;
//log.d(authHeader);
const token = authHeader.split(", ")[1]; // Why is the header formatted like this?
if (!token) return { valid: false } as AuthResult;
const splitToken = token.split(' ')[1];
if (!splitToken) return { valid: false } as AuthResult;
let token: string | undefined;
if (authHeader.substring(0, 6) === 'Bearer') {
const splitToken = authHeader.split(' ');
if (splitToken[1]) token = splitToken[1];
}
if (authHeader.includes(', ')) {
const splitToken = authHeader.split(', ');
if (splitToken[1]) token = splitToken[1];
}
const decodedToken = await decode<ProfileTokenFormat>(splitToken, config.auth.secret, {algorithm: 'HS512'});
const schemaResult = ProfileTokenSchema.safeParse(decodedToken);
if (!schemaResult.success) return { valid: false } as AuthResult;
else return { token: decodedToken, valid: true } as AuthResult;
try {
if (!token) throw new Error("No token provided");
const decodedToken = await decode<ProfileTokenFormat>(token, config.auth.secret, {algorithm: 'HS512'});
const schemaResult = ProfileTokenSchema.safeParse(decodedToken);
if (!schemaResult.success) return { valid: false } as AuthResult;
else return { token: decodedToken, valid: true } as AuthResult;
} catch (err) {
log.w(`Authentication failed`);
log.w((err as Error).message);
return { valid: false } as AuthResult;
}
}
const port = config.web.api.port;
@@ -187,7 +199,7 @@ try {
return response;
} else {
log.e(`401 ${info.remoteAddr} ${req.method} ${req.url}`);
log.e(`401 ${info.remoteAddr.hostname}:${info.remoteAddr.port} ${req.method} ${req.url}`);
return new Response(null, { status: 401 });
}
@@ -200,20 +212,19 @@ try {
Deno.addSignalListener("SIGINT", () => {
if (shuttingDown) return;
shuttingDown = true;
for (const handoff of SocketHandoff.all()) handoff.complete();
for (const sock of UnifiedProfile.getAllSockets()) sock.sendNotification(PushNotificationId.ModerationQuitGame);
});
Deno.addSignalListener("SIGINT", () => {
if (shuttingDown) return;
log.i(`Shutting down`);
abort.abort(); // websockets
http.close();
http.closeAllConnections();
});
Deno.addSignalListener("SIGINT", () => {
for (const socket of UnifiedProfile.getAllSockets()) socket.sendNotification(PushNotificationId.ModerationQuitGame); // untested
});
if (!(await UnifiedProfile.existsByName("Coach"))) UnifiedProfile.create({ username: "Coach", id: 1 }); // create Coach id 1 if they do not exist
if (!(await UnifiedProfile.existsByName("Server"))) UnifiedProfile.create({ username: "Server", id: 2 }); // create Server id 2 if they do not exist
// use these later in development
if (!(await GameConfigs.getGameConfig('splitTestSoftOverrides'))) GameConfigs.setGameConfig('splitTestSoftOverrides', '');
if (!(await GameConfigs.getGameConfig('splitTestHardOverrides'))) GameConfigs.setGameConfig('splitTestHardOverrides', '');

View File

@@ -20,12 +20,9 @@ import express from "express";
import UnifiedProfile, { Profile } from "../../data/profiles.ts";
import { z } from "zod";
import { AuthType } from "../../data/users.ts";
import Logging from "@proxnet/undead-logging";
export const route = APIUtils.createRouter("/account");
const log = new Logging("AccountRoute");
const CreateAccountRequestBodySchema = z.object({
platform: z.string(),
platformId: z.string(),

View File

@@ -58,7 +58,6 @@ route.router.post('/v1/cleargroup',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
express.json(),
APIUtils.logBody,
APIUtils.validateRequestBody(ClearGroupRequestSchema),
(rq: express.Request<NoBody, NoBody, ClearGroupRequestBody>, rs: express.Response) => {

View File

@@ -33,7 +33,6 @@ route.router.get('/v2',
async (_rq, rs) => {
const settings = await rs.locals.profile.Settings.getSettings();
log.d(`settings res: ${JSON.stringify(settings)}`);
rs.json(settings);
}

View File

@@ -108,7 +108,6 @@ route.router.post("/token",
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Web),
express.urlencoded({ extended: true }),
APIUtils.logBody,
APIUtils.validateRequestBody<AuthBodyBase>(TokenRequestBodySchema),
async (

View File

@@ -21,13 +21,10 @@ import express from "express";
import Matchmaking from "../../data/live/base.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";
import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts";
import { SettingKey } from "../../data/content/settings.ts";
const log = new Logging("MatchPlayerRoute");
export const route = APIUtils.createRouter('/player');
interface BaseLoginLock {
@@ -57,7 +54,6 @@ route.router.get('/',
}
rs.json(presExport);
log.d(JSON.stringify(presExport));
}
)