Secure actor-owned game API routes

This commit is contained in:
2026-07-25 08:50:41 +00:00
parent 6889c463fd
commit a9d6541c18
13 changed files with 301 additions and 118 deletions
+7 -3
View File
@@ -99,10 +99,14 @@ export const appRouter = router({
password: input.password,
displayName: input.displayName,
});
await ctx.users.updateRoles(created.id, ['superuser']);
const session = await ctx.sessions.createSession(created);
const bootstrappedUser = {
...created,
roles: ['superuser'],
};
await ctx.users.updateRoles(created.id, bootstrappedUser.roles);
const session = await ctx.sessions.createSession(bootstrappedUser);
return {
user: toPublicUser(created),
user: toPublicUser(bootstrappedUser),
sessionToken: session.sessionToken,
issuedAt: session.issuedAt,
};
+36 -1
View File
@@ -8,6 +8,7 @@ import { createGatewayApiContext } from '../src/context.js';
import { InMemoryProfileStatusService } from '../src/lobby/profileStatusService.js';
import { appRouter } from '../src/router.js';
import type { GatewayPrismaClient } from '@sammo-ts/infra';
import { decryptGameSessionToken } from '@sammo-ts/common/auth/gameToken';
const buildCaller = () => {
const users = createInMemoryUserRepository();
@@ -87,13 +88,47 @@ const buildCaller = () => {
orchestrator,
profileStatus,
requestHeaders: {},
prisma: {} as unknown as GatewayPrismaClient,
prisma: {
appUser: {
findFirst: async () => null,
},
} as unknown as GatewayPrismaClient,
})
);
return { caller, oauthSessions };
};
describe('gateway auth flow', () => {
it('carries the bootstrap superuser role into game sessions', async () => {
const previousToken = process.env.GATEWAY_BOOTSTRAP_TOKEN;
process.env.GATEWAY_BOOTSTRAP_TOKEN = 'bootstrap-test-token';
try {
const { caller } = buildCaller();
const bootstrap = await caller.auth.bootstrapLocal({
token: 'bootstrap-test-token',
username: 'admin',
password: 'secretpass',
displayName: 'Admin',
});
expect(bootstrap.user.roles).toEqual(['superuser']);
const issued = await caller.auth.issueGameSession({
sessionToken: bootstrap.sessionToken,
profile: 'che:default',
});
const payload = decryptGameSessionToken(issued.gameToken, 'test-secret');
expect(payload?.user.roles).toEqual(['superuser']);
} finally {
if (previousToken === undefined) {
delete process.env.GATEWAY_BOOTSTRAP_TOKEN;
} else {
process.env.GATEWAY_BOOTSTRAP_TOKEN = previousToken;
}
}
});
it('registers and issues a game session', async () => {
const { caller, oauthSessions } = buildCaller();
const oauthSession = await oauthSessions.createSession({