[studio] Complete editor uploads and queued cloud builds (#135)

* [studio] Complete editor uploads and queued cloud builds

* [studio] Protect unpublished source and remove unused notification RPC
This commit is contained in:
dukedotdev
2026-10-19 11:04:58 -04:00
committed by GitHub
parent 81c372135c
commit a3291bc9ce
30 changed files with 2553 additions and 142 deletions
+13 -2
View File
@@ -304,7 +304,9 @@ async function authedId(c: Context<App>): Promise<number | null> {
}
/** Whether one account row holds a role. Absent/false both mean no role. */
function holdsRole(account: Account, role: 'developer' | 'moderator'): boolean {
function holdsRole(account: Account, role: 'developer' | 'moderator' | 'screenshare'): boolean {
// Matches accountTokenRoles: screenshare is a feature gate on every account token.
if (role === 'screenshare') return true
return (role === 'developer' ? account.isDeveloper : account.isModerator) === true
}
@@ -314,7 +316,11 @@ function holdsRole(account: Account, role: 'developer' | 'moderator'): boolean {
* mirroring the reference API. An id that is not a number is answered as unknown (404) rather
* than as a 400: `/role/developer/nonsense` is just an id that matches nothing.
*/
async function roleAnswer(c: Context<App>, role: 'developer' | 'moderator', id: string) {
async function roleAnswer(
c: Context<App>,
role: 'developer' | 'moderator' | 'screenshare',
id: string
) {
logger.info(`${role} role lookup`, { id })
const accountId = Number.parseInt(id, 10)
const account = Number.isNaN(accountId) ? null : await getAccount(c.env.DB, accountId)
@@ -1708,6 +1714,11 @@ const app = new Hono<App>()
roleFilterAnswer(c, 'moderator')
)
// Studio queries each room owner's screenshare role before validating video players.
.get('/role/screenshare/:id', describeRoute(roleLookup('screenshare')), (c) =>
roleAnswer(c, 'screenshare', c.req.param('id'))
)
// @guess Oculus nonce. The client asks for this before a Meta login; the exact shape
// it expects hasn't been observed, so this mints a fresh 64-char hex nonce (the length
// Meta's own `GetUserProof` nonces have) and answers it as a bare JSON string. Nothing
+8 -6
View File
@@ -286,16 +286,18 @@ export const RestrictionDto = z.object({
DisplayReason: z.string().nullable().describe('Reason shown to the player. Free text'),
})
export function roleLookup(role: 'developer' | 'moderator') {
export function roleLookup(role: 'developer' | 'moderator' | 'screenshare') {
return {
tags: ['Roles'],
summary: `Whether a player has the ${role} role`,
description:
`Returns a bare JSON boolean (\`true\`/\`false\`), not an object. Off by default and ` +
`granted only by an operator via \`runx admin grant-${role}\`. The same flag also rides ` +
`in the access token's \`role\` claim, so the client rarely needs this route.\n\n` +
`This is the ONE-player form. \`/role/${role}?id=\` is a different question with a ` +
`different answer shape — which of several players hold the role, as an array.`,
role === 'screenshare'
? 'Returns a bare JSON boolean. Screenshare is enabled for every existing account, matching the access-token role policy; unknown accounts return 404. Studio uses this to validate room-owner video permissions.'
: `Returns a bare JSON boolean (\`true\`/\`false\`), not an object. Off by default and ` +
`granted only by an operator via \`runx admin grant-${role}\`. The same flag also rides ` +
`in the access token's \`role\` claim, so the client rarely needs this route.\n\n` +
`This is the ONE-player form. \`/role/${role}?id=\` is a different question with a ` +
`different answer shape — which of several players hold the role, as an array.`,
parameters: [
{
name: 'id',
@@ -1821,6 +1821,22 @@ describe('auth worker routes', () => {
expect(await res.text()).toBe('')
})
test('GET /role/screenshare/:id matches the token feature gate for existing accounts', async () => {
for (const id of [42, 77]) {
const res = await exports.default.fetch(`${ORIGIN}/role/screenshare/${id}`)
expect(res.status).toBe(200)
expect(await res.json()).toBe(true)
}
})
test('GET /role/screenshare/:id does not grant a role to unknown accounts', async () => {
for (const id of ['999999', 'nonsense']) {
const res = await exports.default.fetch(`${ORIGIN}/role/screenshare/${id}`)
expect(res.status).toBe(404)
expect(await res.text()).toBe('')
}
})
test('GET /role/developer/:id returns a bare false for an un-flagged account', async () => {
const res = await exports.default.fetch(`${ORIGIN}/role/developer/42`)
expect(res.status).toBe(200)
@@ -2032,6 +2048,7 @@ describe('auth worker routes', () => {
'GET /role/developer/{id}',
'GET /role/moderator',
'GET /role/moderator/{id}',
'GET /role/screenshare/{id}',
'POST /account/me/changepassword',
'POST /cachedlogin/forplatformid/{platform}/{id}',
'POST /cachedlogin/forplatformids',