mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-29 00:48:52 -07:00
[studio] move cloud_build_key to secrets
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
import { parseMultipart } from './multipart'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
import type { App } from './context'
|
||||
import type { App, Env } from './context'
|
||||
|
||||
export const CLOUD_JOB_SCHEMA = `CREATE TABLE IF NOT EXISTS studio_cloud_build_job (
|
||||
cloud_build_id TEXT PRIMARY KEY, payload TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'queued',
|
||||
@@ -122,7 +122,21 @@ function wire(row: CloudBuildRecord) {
|
||||
: null,
|
||||
}
|
||||
}
|
||||
async function privateAuth(request: Request, key: string | undefined) {
|
||||
/**
|
||||
* The pull worker's credential, resolved per request from the Secrets Store. Returns ''
|
||||
* when cloud building is off: the secret holds the literal `off` (the binding has to
|
||||
* resolve for the worker to deploy, so that is how an operator opts out) or can't be read.
|
||||
* Every gate on "is cloud building configured" goes through here.
|
||||
*/
|
||||
export async function cloudBuildKey(env: Env): Promise<string> {
|
||||
try {
|
||||
const key = (await env.CLOUD_BUILD_KEY.get()) ?? ''
|
||||
return key === 'off' ? '' : key
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
async function privateAuth(request: Request, key: string) {
|
||||
if (!key) return false
|
||||
const wanted = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(`Bearer ${key}`))
|
||||
const got = await crypto.subtle.digest(
|
||||
@@ -188,7 +202,7 @@ const app = new Hono<App>()
|
||||
if (accountId === null) return c.body(null, 401)
|
||||
if (!roles?.includes('betastudio'))
|
||||
return c.json(fail('Studio upload authorization is required.'), 403)
|
||||
if (!c.env.CLOUD_BUILD_KEY) return c.json(fail('Cloud building is not configured.'), 503)
|
||||
if (!(await cloudBuildKey(c.env))) return c.json(fail('Cloud building is not configured.'), 503)
|
||||
const form = await c.req.parseBody()
|
||||
const ids = ['roomId', 'subRoomId', 'subRoomDataSaveId'].map((key) => textField(form[key]))
|
||||
if (ids.some((id) => !ID.test(id) || !Number.isSafeInteger(Number(id))))
|
||||
@@ -358,7 +372,7 @@ const app = new Hono<App>()
|
||||
return c.json(ok(null))
|
||||
})
|
||||
.use('/internal/cloud-builds/*', async (c, next) => {
|
||||
if (!(await privateAuth(c.req.raw, c.env.CLOUD_BUILD_KEY))) return c.body(null, 401)
|
||||
if (!(await privateAuth(c.req.raw, await cloudBuildKey(c.env)))) return c.body(null, 401)
|
||||
return next()
|
||||
})
|
||||
.post('/internal/cloud-builds/claim', async (c) => {
|
||||
@@ -684,7 +698,7 @@ async function enqueueAvatar(
|
||||
!((await avatarStudioFlags(c.env.DB, accountId)) & avatarItemAccessBit(item.OutfitType))
|
||||
)
|
||||
return c.body(null, 403)
|
||||
if (!c.env.CLOUD_BUILD_KEY) return c.json(fail('Cloud building is not configured.'), 503)
|
||||
if (!(await cloudBuildKey(c.env))) return c.json(fail('Cloud building is not configured.'), 503)
|
||||
const pending = await pendingAvatarBuild(c.env.DB, itemId, saveId)
|
||||
if (pending) return c.json(ok(wire(pending)))
|
||||
const object = await c.env.CDN_ASSETS.get('avatarunityasset/' + save.source_filename)
|
||||
|
||||
@@ -17,8 +17,12 @@ export type Env = SharedHonoEnv & {
|
||||
// Shared `recflare-cdn` bucket. Local Studio builds land under
|
||||
// `room/studio/<roomId>/`. The same bucket name the rooms and cdn workers bind.
|
||||
CDN_ASSETS: R2Bucket
|
||||
/** Private pull-worker credential; leave unset to disable cloud build dispatch. */
|
||||
CLOUD_BUILD_KEY?: string
|
||||
/**
|
||||
* Private pull-worker credential, out of the shared Secrets Store (same store as
|
||||
* JWT_SECRET). Read it through `cloudBuildKey()` in cloud-builds.ts, never `.get()`
|
||||
* directly: the value `off` (or an unreadable secret) means cloud building is disabled.
|
||||
*/
|
||||
CLOUD_BUILD_KEY: SecretsStoreSecret
|
||||
CLOUD_BUILD_TARGETS?: string
|
||||
CLOUD_BUILD_TEMPLATE_VERSION?: string
|
||||
/** R2 key of an operator-published template catalog; unset disables downloads. */
|
||||
|
||||
@@ -36,7 +36,7 @@ const manifest = {
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await adminSecretsStore(env.JWT_SECRET).create('test-key')
|
||||
env.CLOUD_BUILD_KEY = 'private-avatar-worker-key'
|
||||
await adminSecretsStore(env.CLOUD_BUILD_KEY).create('private-avatar-worker-key')
|
||||
env.CLOUD_BUILD_TARGETS = '0,2'
|
||||
for (const sql of [
|
||||
...SCHEMA_DDL,
|
||||
|
||||
@@ -49,7 +49,7 @@ async function auth(id = 42, studio = true) {
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await adminSecretsStore(env.JWT_SECRET).create('test-key')
|
||||
env.CLOUD_BUILD_KEY = key
|
||||
await adminSecretsStore(env.CLOUD_BUILD_KEY).create(key)
|
||||
env.CLOUD_BUILD_TARGETS = '0'
|
||||
for (const sql of [
|
||||
...ROOM_SCHEMA_DDL,
|
||||
|
||||
Reference in New Issue
Block a user