working room saves

This commit is contained in:
Devin Zuczek
2026-07-05 22:55:56 -04:00
parent 5a2e3f6e1a
commit 3b9d59239e
10 changed files with 469 additions and 3 deletions
+35
View File
@@ -574,6 +574,41 @@ const app = new Hono<App>({ strict: false })
})
)
// Verify the caller holds at least `role` in a room. Params come from the form
// body (falling back to the query string). Returns a bare `true`/`false`: the
// room creator always passes; otherwise the caller needs a Roles entry with
// `Role >= role`. Any failure (no token, unknown room, insufficient role) is
// `false`. The `context` field (e.g. MakerPen) is accepted and ignored.
.post('/api/rooms/v1/verifyRole', async (c) => {
const body = (await c.req.parseBody().catch(() => ({}))) as Record<string, unknown>
const param = (name: string): string => {
const form = body[name]
if (typeof form === 'string' && form !== '') return form
return c.req.query(name) ?? ''
}
const roomId = Number.parseInt(param('roomId'), 10)
const role = Number.parseInt(param('role'), 10)
const accountId = await authedId(c)
if (accountId === null || Number.isNaN(roomId)) return c.json(false)
const room = await getRoomById(c.env.DB, roomId)
if (!room) return c.json(false)
// The creator always passes.
if (room.CreatorAccountId === accountId) return c.json(true)
// Otherwise the caller needs a room role at least as high as requested.
const roles = Array.isArray(room.Roles)
? (room.Roles as Array<Record<string, unknown>>)
: []
const hasRole = roles.some(
(r) =>
r.AccountId === accountId && typeof r.Role === 'number' && r.Role >= (role || 0)
)
return c.json(hasRole)
})
// ---- Room server ----------------------------------------------------------
// Room data is read from the shared `recflare` D1 (owned by the rooms worker).
// Register specific paths before the `/:id` param route.
+40
View File
@@ -32,6 +32,15 @@ const TEST_ROOMS = [
CreatorAccountId: 1,
SubRooms: [{ SubRoomId: 2 }],
},
{
// Owned by account 1; account 42 holds Role 30 (a co-owner) for verifyRole tests.
RoomId: 3,
Name: 'RoleRoom',
IsDorm: false,
CreatorAccountId: 1,
SubRooms: [{ SubRoomId: 3 }],
Roles: [{ AccountId: 42, Role: 30, LastChangedByAccountId: null, InvitedRole: 0 }],
},
]
beforeAll(async () => {
@@ -315,6 +324,37 @@ describe('room server', () => {
expect(rooms.map((r) => r.Name)).toEqual(['RecCenter'])
})
test('POST /api/rooms/v1/verifyRole checks creator + room roles', async () => {
const verify = async (
fields: Record<string, string>,
sub?: string
): Promise<boolean> => {
const res = await exports.default.fetch(`${ORIGIN}/api/rooms/v1/verifyRole`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...(sub ? await bearer(sub) : {}),
},
body: new URLSearchParams(fields).toString(),
})
expect(res.status).toBe(200)
return (await res.json()) as boolean
}
// No token → false.
expect(await verify({ roomId: '2', role: '255' })).toBe(false)
// Creator (account 1 owns room 2) → true regardless of role.
expect(await verify({ roomId: '2', role: '255', context: 'MakerPen' }, '1')).toBe(true)
// Non-creator with no role in the room → false.
expect(await verify({ roomId: '2', role: '30' }, '42')).toBe(false)
// Account 42 holds Role 30 in room 3 → passes when requesting ≤ 30…
expect(await verify({ roomId: '3', role: '30' }, '42')).toBe(true)
// …but not a higher role.
expect(await verify({ roomId: '3', role: '255' }, '42')).toBe(false)
// Unknown room → false.
expect(await verify({ roomId: '99999', role: '0' }, '42')).toBe(false)
})
test('GET /roomserver/photon_access_token returns permissions + instance id', async () => {
const res = await exports.default.fetch(`${ORIGIN}/roomserver/photon_access_token`)
expect(res.status).toBe(200)