mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[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:
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user