fix sanctions and admin role escalation

This commit is contained in:
2026-07-26 18:11:01 +00:00
parent 3fb75ccf03
commit ab6ed3553a
13 changed files with 591 additions and 51 deletions
+7
View File
@@ -1,5 +1,6 @@
import { TRPCError } from '@trpc/server';
import { decryptGameSessionToken } from '@sammo-ts/common/auth/gameToken';
import { isGameAccessBlocked } from '@sammo-ts/common/auth/sanctions';
import { isAfter, isValid, parseISO } from 'date-fns';
import { z } from 'zod';
@@ -58,6 +59,12 @@ export const authRouter = router({
message: 'Invalid gateway token.',
});
}
if (isGameAccessBlocked(payload.sanctions, [ctx.profile.name, ctx.profile.id])) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Game access is restricted for this account.',
});
}
const flushedAt = ctx.flushStore.getFlushedAt(payload.user.id);
if (flushedAt && new Date(payload.issuedAt) <= flushedAt) {
throw new TRPCError({
+2 -28
View File
@@ -2,6 +2,7 @@ import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { asRecord } from '@sammo-ts/common';
import type { UserSanctions } from '@sammo-ts/common/auth/gameToken';
import { isMessageAccessBlocked } from '@sammo-ts/common/auth/sanctions';
import { authedProcedure, router } from '../../trpc.js';
import {
@@ -47,35 +48,8 @@ const redactDiplomacyMessages = (messages: MessageView[], permission: number): M
});
};
const isFutureDate = (value: string | undefined, now = Date.now()): boolean => {
if (!value) {
return false;
}
const parsed = Date.parse(value);
return Number.isFinite(parsed) && parsed > now;
};
const isMessageFeatureBlocked = (sanctions: UserSanctions, profileNames: string[]): boolean => {
if (
isFutureDate(sanctions.mutedUntil) ||
isFutureDate(sanctions.suspendedUntil) ||
isFutureDate(sanctions.bannedUntil)
) {
return true;
}
for (const profileName of profileNames) {
const restriction = sanctions.serverRestrictions?.[profileName];
if (!restriction) {
continue;
}
if (restriction.until && !isFutureDate(restriction.until)) {
continue;
}
if (restriction.blockedFeatures?.includes('messages')) {
return true;
}
}
return false;
return isMessageAccessBlocked(sanctions, profileNames);
};
const readPenaltyNumber = (penalty: unknown, key: string, fallback: number): number => {
+8
View File
@@ -1,5 +1,6 @@
import { randomUUID } from 'node:crypto';
import { initTRPC, TRPCError } from '@trpc/server';
import { isGameAccessBlocked } from '@sammo-ts/common/auth/sanctions';
import type { GameApiContext } from './context.js';
import { IdempotentTurnDaemonTransport } from './daemon/idempotentTransport.js';
@@ -14,6 +15,13 @@ const requireAuthMiddleware = t.middleware(({ ctx, next }) => {
message: 'Unauthorized',
});
}
const profileNames = ctx.profile ? [ctx.profile.name, ctx.profile.id] : [];
if (isGameAccessBlocked(ctx.auth.sanctions, profileNames)) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Game access is restricted for this account.',
});
}
return next({
ctx: {
...ctx,