import { Weave, WeaveError, FILE_MAX_BYTES, PREVIEW_MAX_BYTES, mbText, fileHeaders, isHtmlFile, viewHtml, frameLinkBase, frameStorageScript, FRAME_HEIGHT_SCRIPT, sandboxedHtmlHeaders, HTML_PAGE_POLICY, logoType, inviteUrl, accountActor } from './engine.js'; import { handleApplet, appletOn } from './applet.js'; import { storageFull, statusFor, errorBody, nonNegativeInteger } from './errors.js'; import { matchRoute, httpReply, boundedLimit, previewInput } from './commands.js'; import { HOOK_BODY_MAX } from './delivery.js'; export { storageFull, statusFor, errorBody }; import { shareDoor, shareGate, sharePage, scopedMention, sharedRead, withinScope } from './shares.js'; import { vocabularyView } from './vocabulary.js'; import { renderDocumentPage, renderMarkdown, isHtmlDocument, escapeHtml } from './markdown.js'; import { markdownToPdf } from './pdf.js'; const deckModule = () => import('./deck.js'); import { handleMcpMessage, mayAdminister, toolTable, QUERY_PAGE, SEATLESS_TOOLS } from './mcp.js'; import { workspaceSlug, slugOfHost, slugTaken } from './workspace-name.js'; import { createForm, updateForm, getFormDefinition, getForm, listForms, submitForm, ensureBugForm, formAdmits, renderFormPage, leaveWorkspace, isDocsWorkspace, refuseOnDocs } from './forms.js'; import { renderAuthPage, renderRefusalPage, renderMcpDoorPage, sameOriginPath } from './auth-page.js'; import { CLIENT_FAULTS } from './oidc.js'; import { PRIVACY, TERMS, SUPPORT, AGENT_DOCS } from './legal.js'; import { bugForwardTarget, forwardBugReport } from './bugreport.js'; import { inviteEmail, trialEmail, ROLES as MAIL_ROLES, longDate } from './mail.js'; import { BillingUnavailable, PAID_ROLES } from './billing.js'; import { ANONYMOUS, viewerFor, spaceRole, workspaceRole, topRole, withViewers, unrestricted } from './access.js'; import '../public/starter-core.js'; import '../public/prefill-core.js'; const { WeaveStarters, WeaveSlugs, weavePrefill } = globalThis; export const decodePath = (pathname) => { try { return decodeURIComponent(pathname.replace(/%2f/gi, '%252F')); } catch { throw new WeaveError('Malformed percent-escape in the path', 'invalid'); } }; export const ifMatchRevision = (header) => { const tag = String(header ?? '').split(',')[0].trim().replace(/^W\//, '').replace(/^"|"$/g, ''); return tag && tag !== '*' ? tag : null; }; export const ownsEntity = (ws, id) => Object.hasOwn(ws.state.entities, id); const STARTED_AT = new Date().toISOString(); export const clientAddress = ({ forwarded, remote, trustProxy }) => { const hop = trustProxy ? String(forwarded ?? '').split(',').pop().trim() : ''; return hop || remote || 'unknown'; }; export const MAX_BODY_BYTES = 10 * 1024 * 1024; export const MAX_ATTACH_BODY_BYTES = Math.ceil(((FILE_MAX_BYTES + PREVIEW_MAX_BYTES) * 4) / 3) + 64 * 1024; const ATTACH_ROUTE = /^\/api\/entities\/[^/]+\/(?:fields\/[^/]+\/)?files$/; const actorOf = (account) => account?.visitor ? `visitor: ${account.name}` : account?.id ? accountActor(account.id) : account?.name; const prefersHtml = (accept) => { const q = {}; for (const part of String(accept ?? '').split(',')) { const [type, ...params] = part.trim().toLowerCase().split(';'); const weight = params.map((p) => p.trim().match(/^q=([\d.]+)$/)).find(Boolean); q[type.trim()] = weight ? Number(weight[1]) : 1; } return (q['text/html'] ?? 0) > 0 && (q['text/html'] ?? 0) > (q['application/json'] ?? 0); }; const START_LINK = /^\/api\/start\/open\/([^/?#\\]+)(?:\?[^#\\]*)?$/; const INVITE_LINK_FIELDS = new Set(['invite', 'handoff', 'next']); const inviteLink = (link) => { const m = /^(?:\/w\/[^/?#\\]+)?\/api\/auth\/oidc\/start\?([^#\\]*)$/.exec(link ?? ''); if (!m) return false; const q = new URLSearchParams(m[1]); return q.has('invite') && [...q.keys()].every((k) => INVITE_LINK_FIELDS.has(k)); }; const WORKSPACE_LINK = /^\/w\/[^/?#\\]+\/(?!api\/)/; const hostPath = (raw) => { const p = sameOriginPath(raw, 'http://host.invalid', null); return p === raw && !/^\/(?:api|mcp)(?:[/?#]|$)/.test(p) ? p : null; }; const wallPageHtml =(authHref) => `Sign in required

This workspace requires authentication

Sign in, send a Bearer token, or open a share link you were given.

`; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; const clip = (s, n) => (s.length > n ? s.slice(0, n - 1).trimEnd() + '…' : s); const count = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`; const metaAttr = (s) => escapeHtml(s).replace(/\r?\n/g, ' '); const PREFILL_SKIP = new Set(['lookup', 'rollup', 'formula', 'view', 'attachments', 'avatar', 'field']); const pickOption = (options, ref) => options.find((o) => o.id === ref) ?? options.find((o) => o.name === ref) ?? options.find((o) => o.name.toLowerCase() === String(ref).toLowerCase()); function prefillDraft(weave, ref, params) { const db = weave.getTable(ref); const { pairs, cut } = weavePrefill.clip(params); const slots = new Map(); const unknown = []; for (const [key, raw] of pairs) { const f = weave.findField(db, key); if (!f || PREFILL_SKIP.has(f.type)) { if (!unknown.includes(key)) unknown.push(key); continue; } if (!slots.has(f.id)) slots.set(f.id, { f, raws: [] }); slots.get(f.id).raws.push(raw); } const fields = [...slots.values()].map(({ f, raws }) => { const errors = []; const out = { field: f.name, type: f.type, errors }; const list = f.type === 'multiselect' || (f.type === 'relation' && f.config.many); const picked = list ? raws : raws.slice(0, 1); if (f.type === 'select' || f.type === 'multiselect' || f.type === 'workflow') { const choices = f.type === 'workflow' ? f.config.states : f.config.options; const names = picked.map((r) => { const opt = pickOption(choices, r); if (!opt) errors.push(`'${r}' is not ${f.type === 'workflow' ? 'a state' : 'an option'} of '${f.name}'`); return opt ? opt.name : r; }); out.value = list ? names : names[0]; return out; } if (f.type === 'relation') { out.rows = []; const names = []; for (const r of picked) { let hit = null; for (const dbId of weave.relationTargetDbIds(f)) { try { hit = weave.findEntity(dbId, r); } catch { hit = null; } if (hit && !hit.deletedAt) break; hit = null; } if (hit) out.rows.push({ id: hit.id, name: weave.entityName(hit) }); else errors.push(`No record named '${r}' in the related table`); names.push(hit ? weave.entityName(hit) : r); } out.value = list ? names : names[0]; return out; } out.value = picked[0]; return out; }); return { table: db.id, name: db.name, fields, unknown, cut }; } const RASTER = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']); const plainLine = (md) => String(md ?? '').split('\n').map((l) => l.replace(/[*_`#>]/g, '').trim()).find(Boolean) ?? ''; const previewPageHtml = (head, authHref) => `${head}

Sign in to open this in weave.

`; const CHALLENGE_TTL_MS = 5 * 60 * 1000; const HANDOFF_TTL_MS = 60 * 1000; const digest = async (s) => Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))), (b) => b.toString(16).padStart(2, '0')).join(''); const LIMITS = { options: 10, failed: 5, slugs: 60, paint: 6, paintAll: 600 }; const START_TTL_MS = 30 * 60 * 1000; const RECENT_CAP = 50; const RECENT_NAME = /^[a-z0-9_.-]+$/; const holdsLink = (w, kind, id) => { w.maybeRefresh(); if (kind === 't') return !!w.findTable(id); if (kind === 's') return !!(w.findSpace(id) || w.shareByToken(id)); return Object.hasOwn(w.state.entities ?? {}, id); }; const linkHolder = (hub, kind, id) => hub.entries().map(([, w]) => w).find((w) => !w.state.meta.deletedAt && holdsLink(w, kind, id)) ?? null; const INGEST_MAX_BYTES = 2 * 1024 * 1024; const INGEST_HEADERS = ['content-type', 'content-encoding', 'user-agent']; const PAINT_MAX_BYTES = 8 * 1024; const PAINT_ENGINES = new Set(['blink', 'gecko', 'webkit', 'other']); const paintNumber = (v, max) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= max ? v : null); const paintText = (v, max, shape) => (typeof v === 'string' && v.length <= max && shape.test(v) ? v : null); function paintReports(body) { if (!body || typeof body !== 'object' || !Array.isArray(body.reports) || !body.reports.length || body.reports.length > 10) return null; const common = { build: paintText(body.build, 40, /^[0-9A-Za-z.+-]+$/), engine: PAINT_ENGINES.has(body.engine) ? body.engine : null, vw: paintNumber(body.vw, 20000), vh: paintNumber(body.vh, 20000) }; const rows = []; for (const r of body.reports) { const route = paintText(r?.route, 120, /^[A-Za-z0-9/:#._-]*$/); if (r?.kind === 'shift') rows.push({ kind: 'shift', score: paintNumber(r.score, 100), move: paintNumber(r.move, 100000), selector: paintText(r.selector, 200, /^[A-Za-z0-9 >.#_-]*$/), route, ...common }); else if (r?.kind === 'frame') rows.push({ kind: 'frame', ms: paintNumber(r.ms, 600000), blocking: paintNumber(r.blocking, 600000), route, ...common }); else return null; } return rows; } const newChallenge = () => btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const joinedInvite = (next) => { const i = next.indexOf('#'); const [path, hash] = i < 0 ? [next, ''] : [next.slice(0, i), next.slice(i)]; return `${path}${path.includes('?') ? '&' : '?'}joined=invite${hash}`; }; const parseCookies = (header) => Object.fromEntries(String(header ?? '').split(';').map((c) => c.trim()).filter(Boolean).map((c) => { const i = c.indexOf('='); return i < 0 ? [c, ''] : [c.slice(0, i), c.slice(i + 1)]; })); export function createRequestHandler(hub, { version = 'unknown', uptime = () => 0, build = () => null, backup = () => null, cloudBackups = null, serveStatic = null, origin = null, trustProxy = false, limits = LIMITS, oidc = null, mcpOrigins = [], mail = null, baseDomain = null, openSignup = { on: false, limit: 1 }, clerkKey = null, billing = null, posthog = null, paintLog = null, signIn = () => null, anonymousForms = ['1', 'true'].includes(String(process.env.WEAVE_ANONYMOUS_FORMS ?? '').toLowerCase()) } = {}) { const clerkPane = (() => { if (!clerkKey || !oidc) return null; let host; try { host = atob(clerkKey.split('_')[2]).replace(/\$$/, ''); } catch { return null; } const issuer = new URL(oidc.issuer); return issuer.host === host ? { publishableKey: clerkKey, script: `${issuer.origin}/npm/@clerk/clerk-js@6/dist/clerk.browser.js` } : null; })(); const isProviderReturn = (raw) => { if (!clerkPane) return false; try { const u = new URL(raw); return u.origin === new URL(oidc.issuer).origin && raw.startsWith(`${u.origin}/`) && u.pathname.startsWith('/oauth/'); } catch { return false; } }; const challenges = new Map(); const handoffs = new Map(); const uploads = new Map(); const spentUploads = new Map(); const remirror = async (w) => { const orgId = billing ? w.billing().billing.orgId : null; if (!orgId) return null; try { return await billing.syncMembers({ orgId, accounts: w.listAccounts() }); } catch (err) { console.error(`weave: the Clerk organization ${orgId} was not re-mirrored: ${err.message}`); return null; } }; const scheme = origin ? new URL(origin).protocol : 'http:'; const apexOrigin = origin ?? `${scheme}//${baseDomain}`; const hostSuffix = `.${baseDomain}${origin && new URL(origin).port ? `:${new URL(origin).port}` : ''}`; const rates = { options: new Map(), failed: new Map(), slugs: new Map(), paint: new Map(), paintAll: new Map() }; limits = { ...LIMITS, ...limits }; const starts = new Map(); const formRates = new Map(); const formLimited = (keys) => { const now = Date.now(); for (const [key, value] of formRates) if (value.until <= now) formRates.delete(key); let retry = 0; for (const [key, max] of keys) { let hit = formRates.get(key); if (!hit) { if (formRates.size >= 10000) return 60; hit = { until: now + 60000, n: 0 }; formRates.set(key, hit); } hit.n += 1; if (hit.n > max) retry = Math.max(retry, Math.ceil((hit.until - now) / 1000)); } return retry; }; const prune = (map) => { const now = Date.now(); for (const [k, v] of map) if (v.expiresAt <= now) map.delete(k); }; const handOff = async (engine, accountId, next, host) => { const code = newChallenge(); prune(handoffs); handoffs.set(await digest(code), { engine, accountId, next, host, expiresAt: Date.now() + HANDOFF_TTL_MS }); return { status: 302, headers: { Location: `${scheme}//${host}${hostSuffix}/api/auth/handoff?code=${code}`, 'Cache-Control': 'no-store' }, body: '' }; }; const limited = (kind, ip, { peek = false } = {}) => { const now = Date.now(); const bucket = rates[kind]; if (bucket.size > 5000) bucket.clear(); let hit = bucket.get(ip); if (!hit || now - hit.at > 60 * 1000) { hit = { at: now, n: 0 }; bucket.set(ip, hit); } if (!peek) hit.n += 1; return hit.n > limits[kind] || (peek && hit.n >= limits[kind]); }; const noteFailure = (ip) => limited('failed', ip); const putChallenge = (entry, id = newChallenge()) => { const now = Date.now(); for (const [k, v] of challenges) if (v.expiresAt <= now) challenges.delete(k); challenges.set(id, { ...entry, expiresAt: now + CHALLENGE_TTL_MS }); return id; }; const takeChallenge = (id, kind) => { const c = challenges.get(id); challenges.delete(id); if (!c || c.kind !== kind || c.expiresAt <= Date.now()) return null; return c; }; const originFor = (rx) => { if (origin) return origin; const port = String(rx.header('host') ?? '').split(':')[1]; return `http://localhost${port ? ':' + port : ''}`; }; const mcpOrigin = (rx) => { const known = [origin, ...mcpOrigins].filter(Boolean); const host = String(rx.header('host') ?? '').toLowerCase(); return known.find((o) => new URL(o).host === host) ?? known[0] ?? originFor(rx); }; const clientIp = (rx) => clientAddress({ forwarded: rx.header('x-forwarded-for'), remote: rx.remote, trustProxy }); const LEGACY_COOKIE = 'wv_session'; const cookieName = (w) => `${LEGACY_COOKIE}_${w.state.meta.id}`; const outName = (w) => `wv_out_${w.state.meta.id}`; const secureFlag = (rx) => (originFor(rx).startsWith('https:') ? '; Secure' : ''); const sessionCookie = (name, token, rx) => `${name}=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${Weave.SESSION_TTL_MS / 1000}${secureFlag(rx)}`; const clearCookie = (name, rx) => `${name}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0${secureFlag(rx)}`; const withCookies = (res, jar) => { if (!res || !jar.length) return res; const own = [res.headers?.['Set-Cookie'] ?? []].flat(); const named = new Set(own.map((c) => c.split('=')[0])); const add = new Map(jar.filter((c) => !named.has(c.split('=')[0])).map((c) => [c.split('=')[0], c])); const all = [...own, ...add.values()]; return { ...res, headers: { ...res.headers, 'Set-Cookie': all.length === 1 ? all[0] : all } }; }; const respond = async (rx, jar, viewers) => { let path = rx.path; const viewerZone = rx.header('x-weave-zone') || null; let versionOf = null; const out = (status, data, headers = {}) => { const isBin = data instanceof Uint8Array; const isStr = typeof data === 'string'; let schemaVersion = null; try { schemaVersion = versionOf?.schemaVersion?.() ?? null; } catch {} return { status, headers: { 'Content-Type': headers['Content-Type'] ?? (isBin || isStr ? 'text/plain; charset=utf-8' : 'application/json'), ...(schemaVersion ? { 'X-Weave-Schema-Version': schemaVersion } : {}), ...headers, }, body: isBin || isStr ? data : JSON.stringify(data, null, 1), }; }; const notFound = (json) => { const isPage = rx.method === 'GET' && serveStatic && !path.includes('/api/'); const page = isPage ? serveStatic('/404.html') : null; return page ? { ...page, status: 404 } : out(404, json); }; if (path === '/api/paint') { if (!paintLog || !rx.readBytes) return out(404, { error: 'Not found', code: 'not-found' }); if (rx.method !== 'POST') return out(405, { error: 'POST only', code: 'method-not-allowed' }, { Allow: 'POST' }); if (limited('paint', clientIp(rx)) || limited('paintAll', '*')) return out(429, { error: 'Too many paint reports: wait a minute', code: 'rate-limited' }, { 'Retry-After': '60' }); let rows; try { rows = paintReports(JSON.parse(Buffer.from(await rx.readBytes(PAINT_MAX_BYTES)).toString('utf8'))); } catch (err) { if (err instanceof WeaveError) return out(statusFor(err), errorBody(err)); rows = null; } if (!rows) return out(400, { error: 'A paint report is JSON with 1 to 10 shift or frame reports', code: 'invalid' }); for (const row of rows) paintLog(`weave: paint ${JSON.stringify(row)}`); return { status: 204, headers: {}, body: '' }; } if (path === '/ingest' || path.startsWith('/ingest/')) { if (!posthog || !rx.readBytes) return out(404, { error: 'Not found', code: 'not-found' }); const rest = path.slice('/ingest'.length) || '/'; const query = rx.searchParams?.toString(); const target = `${/^\/(?:static|array)\//.test(rest) ? posthog.assets : posthog.host}${rest}${query ? `?${query}` : ''}`; const headers = Object.fromEntries(INGEST_HEADERS.map((h) => [h, rx.header(h)]).filter(([, v]) => v)); headers['x-forwarded-for'] = clientIp(rx); let res; try { const body = ['GET', 'HEAD'].includes(rx.method) ? undefined : await rx.readBytes(INGEST_MAX_BYTES); res = await fetch(target, { method: rx.method, headers, body, redirect: 'manual', signal: AbortSignal.timeout(10000) }); } catch (err) { if (err instanceof WeaveError) return out(statusFor(err), errorBody(err)); return out(502, { error: 'PostHog is not answering', code: 'bad-gateway' }); } const kept = Object.fromEntries(['content-type', 'cache-control'].map((h) => [h, res.headers.get(h)]).filter(([, v]) => v).map(([h, v]) => [h === 'content-type' ? 'Content-Type' : 'Cache-Control', v])); return { status: res.status, headers: { 'Content-Type': 'application/octet-stream', ...kept }, body: new Uint8Array(await res.arrayBuffer()) }; } let weave = hub.get(hub.defaultName); let wsPrefix = ''; const hostSlug = slugOfHost(rx.header('host'), baseDomain); let hostOrigin = null; let hostWs = null; if (hostSlug !== null) { const holder = hub.bySlug(hostSlug); if (!holder) { const moved = hub.aliasOf(hostSlug); const q = rx.searchParams?.toString(); if (moved) return { status: 301, headers: { Location: `${scheme}//${String(moved.state.meta.name).toLowerCase()}${hostSuffix}${encodeURI(rx.path)}${q ? `?${q}` : ''}`, 'Cache-Control': 'no-store' }, body: '' }; return notFound({ error: 'Not found', code: 'not-found' }); } weave = holder; hostWs = holder; hostOrigin = `${scheme}//${String(rx.header('host')).toLowerCase()}`; } const wsM = path.match(/^\/w\/([^/]+)(\/.*|$)/); if (wsM && wsM[1] !== 'undefined') { const target = hub.get(wsM[1]) ?? hub.entries().find(([, w]) => w.state.meta.id === wsM[1])?.[1] ?? (wsM[1] === 'weaver' ? hub.get('weave') : null); if (!target || target.state.meta.deletedAt) return notFound({ error: `Workspace '${wsM[1]}' not found`, code: 'not-found' }); weave = target; wsPrefix = `/w/${wsM[1]}`; path = wsM[2] || '/'; } if (baseDomain && oidc && hostWs && wsPrefix && weave !== hostWs && ['GET', 'HEAD'].includes(rx.method) && !/^\/(?:api|mcp|\.well-known)(?:\/|$)/.test(path) && !/\.(css|js|mjs|map|woff2?|ttf|otf|svg|png|jpe?g|gif|webp|ico)$/i.test(path)) { const q = rx.searchParams?.toString(); return { status: 301, headers: { Location: `${scheme}//${String(weave.state.meta.name).toLowerCase()}${hostSuffix}${encodeURI(path)}${q ? `?${q}` : ''}`, 'Cache-Control': 'no-store' }, body: '' }; } weave.maybeRefresh(); versionOf = weave; const hookDoor = rx.method === 'POST' ? matchRoute('POST', path) : null; if (hookDoor?.cmd.http.door) { let bytes = null; try { bytes = rx.readBytes ? await rx.readBytes(HOOK_BODY_MAX) : Buffer.from(JSON.stringify(await rx.readBody())); } catch (err) { if (!(err instanceof WeaveError) || err.code !== 'too-large') throw err; } const r = hookDoor.cmd.run(weave, hookDoor.params, { hook: { bytes, header: (n) => rx.header(n), size: Number(rx.header('content-length')) } }); return out(r.status, r.answer, { 'Cache-Control': 'no-store' }); } if (baseDomain && oidc && !hostOrigin && !wsPrefix && ['GET', 'HEAD'].includes(rx.method)) { if (/^\/[est]\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(path)) { const holder = linkHolder(hub, path.slice(1, 2), path.slice(3)) ?? hub.get(hub.defaultName); return { status: 301, headers: { Location: `${scheme}//${String(holder.state.meta.name).toLowerCase()}${hostSuffix}${path}`, 'Cache-Control': 'no-store' }, body: '' }; } } if (!wsPrefix && ['GET', 'HEAD'].includes(rx.method) && /^\/[est]\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(path)) { const kind = path.slice(1, 2); const id = path.slice(3); const servesHere = holdsLink(weave, kind, id) || (weave.registryHost && holdsLink(weave.registryHost, kind, id)); const holder = servesHere ? null : linkHolder(hub, kind, id); if (holder && hostOrigin) { return { status: 301, headers: { Location: `${scheme}//${String(holder.state.meta.name).toLowerCase()}${hostSuffix}${path}`, 'Cache-Control': 'no-store' }, body: '' }; } if (holder) { const key = hub.list().find((x) => x.id === holder.state.meta.id)?.name ?? holder.state.meta.name; return { status: 302, headers: { Location: `/w/${encodeURIComponent(key)}${path}`, 'Cache-Control': 'no-store' }, body: '' }; } } const updateWorkspace = (patch) => { const slug = patch.name != null ? workspaceSlug(patch.name) : null; const state = slug != null && patch.name !== weave.state.meta.name ? hub.slugState(slug, weave) : 'available'; if (state !== 'available') throw new WeaveError(WeaveSlugs.apiMessage(state, slug), `slug_${state}`); const was = weave.state.meta.name; const ws = weave.updateWorkspace(patch); if (ws.name !== was) hub.rename(was, ws.name); return ws; }; const member = weave; let actor = String(rx.header('x-weave-actor') || 'web').slice(0, 120); weave.actor = actor; if (oidc?.issuer) weave.inviteIssuer = oidc.issuer; path = path.replace(/^(\/api\/tables\/)([^/]+)/, (whole, head, key) => { if (!/%2f/i.test(key)) return whole; try { return head + (weave.findTable(key.replace(/%2f/gi, '/'))?.id ?? key); } catch { return whole; } }); let mcpChallenge = null; const deny = (code, error) => out(code, { error, code: code === 401 ? 'unauthorized' : 'forbidden' }, code === 401 && mcpChallenge ? { 'WWW-Authenticate': mcpChallenge } : {}); { const shareM = path.match(/^\/view\/([A-Za-z0-9_-]+)$/); if (shareM && rx.method === 'GET') { if (!weave.shareByToken(shareM[1])) return out(404, 'This share link is not (or no longer) valid.'); return { status: 302, headers: { Location: `${wsPrefix}/s/${shareM[1]}`, 'Cache-Control': 'no-store' }, body: '' }; } } if (path === '/t' || (path.startsWith('/t/') && !UUID_RE.test(path.slice(3)) && !UUID_RE.test(path.slice(3).replace(/\/new$/, '')))) { const wantsBody = ['POST', 'PUT', 'PATCH'].includes(rx.method) && appletOn(); if (wantsBody && Number(rx.header('content-length') ?? 0) > MAX_BODY_BYTES) return out(413, { error: 'Body too large', code: 'too-large' }); const appletBody = wantsBody ? await rx.readBody().catch(() => ({})) : {}; try { const hit = handleApplet({ weave, rx: { method: rx.method, header: (n) => rx.header(n), searchParams: rx.searchParams, body: appletBody }, path, out, mount: `${wsPrefix}/t`, }); if (hit) return hit; } catch (err) { const json = errorBody(err, 'error'); return err instanceof WeaveError && err.code === 'not-found' ? notFound(json) : out(statusFor(err), json); } } const shellMarker = () => `\n`; const linkPreview = (p, { signedOut = false } = {}) => { const m = p.match(/^\/([est])\/([^/]+)$/); if (p !== '/' && !m) return null; if (signedOut && m && !UUID_RE.test(m[2])) return null; const ws = weave.state.meta; const site = ws.title ?? ws.name; const canonical = baseDomain && ws.name ? `${scheme}//${String(ws.name).toLowerCase()}${hostSuffix}` : null; const base = canonical ?? origin ?? `http://${rx.header('host')}`; const home = canonical ? '' : `/w/${ws.name || hub.defaultName}`; let image = `${base}/brand/weave-mark-512.png`; if (ws.logo) { try { const { meta: logo, bytes } = weave.getWorkspaceLogo(); if (RASTER.has(logoType(bytes))) image = `${base}${home}/api/workspace/logo?v=${logo.id.slice(0, 8)}`; } catch {} } const live = (tables) => tables.reduce((n, d) => n + weave.listEntities(d.id).length, 0); let title, heading, trail, detail, url, route = null, fields = []; try { if (!m) { const tables = weave.userTables(); title = site; detail = plainLine(ws.description) || `${count(new Set(tables.map((d) => d.spaceId)).size, 'space')} · ${count(tables.length, 'table')}`; url = `${home}/`; } else if (m[1] === 'e') { const e = weave.getEntity(m[2]); if (e.deletedAt) return null; const db = weave.state.tables[e.dbId]; const name = weave.entityName(e).trim(); const ref = `${db.name} #${e.publicId}`; title = clip(`${ref}${name ? ` · ${name}` : ''}`, 80); heading = clip(name || ref, 80); trail = [site, weave.state.spaces[db.spaceId]?.name, ref]; fields = weave.previewFields(e.id); detail = fields.map((f) => `${f.label} ${f.value}`).join(' · '); url = `/e/${e.id}`; route = `${wsPrefix}/#/entity/${e.id}`; } else if (m[1] === 's') { const sp = Object.hasOwn(weave.state.spaces, m[2]) ? weave.state.spaces[m[2]] : null; if (!sp || sp.deletedAt) return null; const tables = weave.listTables(sp.id); title = `${sp.name} · space`; trail = [site, sp.name]; detail = `${count(tables.length, 'table')} · ${count(live(tables), 'record')}`; url = `/s/${sp.id}`; route = `${wsPrefix}/#/space/${sp.id}`; } else { const db = weave.listTables().find((d) => d.id === m[2]); if (!db) return null; const space = weave.state.spaces[db.spaceId]?.name; title = `${space} / ${db.name} · table`; trail = [site, space, db.name]; detail = count(live([db]), 'record'); url = `/t/${db.id}`; route = `${wsPrefix}/#/table/${db.id}`; } } catch (err) { if (err instanceof WeaveError) return null; throw err; } const description = m ? `${trail.filter(Boolean).join(' › ')}${detail ? `\n${detail}` : ''}` : detail; const tag = (attr, key, value) => ``; const head = [ `${escapeHtml(title === site ? site : `${title} · ${site}`)}`, tag('name', 'description', description), tag('property', 'og:title', heading ?? title), tag('property', 'og:description', description), tag('property', 'og:site_name', site), tag('property', 'og:url', base + url), tag('property', 'og:type', m?.[1] === 'e' ? 'article' : 'website'), tag('property', 'og:image', image), tag('name', 'twitter:card', 'summary'), ...fields.flatMap((f, i) => [tag('name', `twitter:label${i + 1}`, f.label), tag('name', `twitter:data${i + 1}`, String(f.value))]), ].join('\n'); return { head, route }; }; const carried = weavePrefill.clip(rx.searchParams).query; const authHref = `${wsPrefix}/auth?next=${encodeURIComponent(wsPrefix + path + (carried ? `?${carried}` : ''))}`; const wallPage = () => out(401, wallPageHtml(authHref), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); const wall = () => (oidc ? { status: 302, headers: { Location: authHref, 'Cache-Control': 'no-store' }, body: '' } : wallPage()); const statusPage = ['GET', 'HEAD'].includes(rx.method) && (path === '/status' || path === '/status/'); const legalPage = ['GET', 'HEAD'].includes(rx.method) && (path === '/privacy' || path === '/terms' || path === '/support' || path === '/docs'); const previewLogo = path === '/api/workspace/logo' && ['GET', 'HEAD'].includes(rx.method) && !!weave.state.meta.linkPreview; const startDoor = path === '/start' || path === '/api/start' || path.startsWith('/api/start/'); const formDoor = (['GET', 'HEAD'].includes(rx.method) && /^\/(?:f|api\/forms)\/[^/]+$/.test(path)) || (rx.method === 'POST' && /^\/api\/forms\/[^/]+\/submit$/.test(path)); const openDoor = path === '/api/health' || path === '/auth' || path.startsWith('/api/auth/') || legalPage || statusPage || previewLogo || startDoor || formDoor || (['GET', 'HEAD'].includes(rx.method) && (/\.(css|js|mjs|map|woff2?|ttf|otf|svg|png|jpe?g|gif|webp|ico)$/i.test(path) || /^\/brand\/[\w-]+\.html$/.test(path))); let role = null; let session = null; let accountName = null; let requestAccount = null; const cookies = parseCookies(rx.header('cookie')); if (cookies[LEGACY_COOKIE]) jar.push(clearCookie(LEGACY_COOKIE, rx)); const startRoot = baseDomain ? apexOrigin : ''; const startReturn = (back) => sameOriginPath(back, originFor(rx), null) === back && (WORKSPACE_LINK.test(back) || START_LINK.test(back) || inviteLink(back)); const sameLogin = (a, b) => a.issuer === b.issuer && a.subject === b.subject; const findWorkspaces = (logins) => { const all = hub.entries(); const live = all.filter(([, w]) => !w.state.meta.deletedAt); const accountFor = (w, me) => w.accountForIdentity(me); const opener = (w) => logins.find((me) => accountFor(w, me)) ?? null; const label = (me) => me.email ?? live.map(([, w]) => w.accountForIdentity(me)?.name).find(Boolean) ?? me.subject; const title = (name, w) => w.state.meta.title ?? name; const seenOn = (w, account) => w.listSessions(account.id)[0]?.lastSeenAt ?? null; const logoOf = (name, w) => (w.state.meta.logo ? `/api/start/logo/${encodeURIComponent(name)}?v=${w.state.meta.logo.id.slice(0, 8)}` : null); const rows = [ ...live.filter(([, w]) => opener(w)) .map(([name, w]) => { const me = opener(w); const account = accountFor(w, me); return { kind: 'workspace', name, id: w.state.meta.id, title: title(name, w), login: label(me), host: baseDomain ? `${name.toLowerCase()}.${baseDomain}` : null, role: Weave.roleName(account.role), logo: logoOf(name, w), seenAt: seenOn(w, account), open: `/api/start/open/${encodeURIComponent(name)}` }; }) .sort((a, b) => (b.seenAt ?? '').localeCompare(a.seenAt ?? '')), ...live.flatMap(([name, w]) => logins.filter((me) => me.email && !w.accountForIdentity(me)) .flatMap((me) => w.invitesForEmail(me.email).map((i) => ({ kind: 'invite', name, title: title(name, w), role: i.role, invitedBy: i.invitedBy ?? null, login: label(me), accept: `/api/start/invites/${encodeURIComponent(name)}/${i.id}` })))), ]; const available = rows.filter((r) => r.kind === 'workspace' && !isDocsWorkspace(hub.get(r.name))).map((r) => r.name.toLowerCase()); const names = [...new Set([...String(cookies.wv_rail ?? '').split(',').filter((n) => available.includes(n)), ...available])]; const signedUp = (me) => all.filter(([, w]) => w.state.meta.signup && sameLogin(w.state.meta.signup, me)).length; const signup = openSignup.on ? logins.find((me) => signedUp(me) < openSignup.limit) ?? null : null; const owner = signup ?? (billing ? logins[0] ?? null : null); const accountName = logins.flatMap((me) => live.map(([, w]) => accountFor(w, me)?.name)).find(Boolean) ?? logins.map((me) => me.name).find(Boolean) ?? logins.map(label).find((l) => !l.includes('@')) ?? logins.map((me) => me.email?.split('@')[0]).find(Boolean) ?? null; const unpaid = async (me) => { for (const [name, w] of all) { if (Weave.roleName(w.accountForIdentity(me)?.role) !== 'architect') continue; const { plan } = await billing.standing({ workspace: w.billing(), accounts: w.listAccounts() }); if (plan == null || plan === 'trial') return name; } return null; }; return { rows, names, logins: logins.map(label), accountIn: (w) => { const me = opener(w); return me ? { ...accountFor(w, me), login: me } : null; }, signup, owner, canCreate: !!owner, accountName, unpaid }; }; const creationAdmission = async (logins) => { const found = findWorkspaces(logins); const me = found.owner; if (!me) return { response: out(403, { error: openSignup.on ? 'This sign-in has made as many workspaces as this server allows.' : 'Creating a workspace here needs an invite from the operator.', code: openSignup.on ? 'signup_limit' : 'signup_closed' }) }; if (billing) { let waiting; try { waiting = await found.unpaid(me); } catch (error) { if (!(error instanceof BillingUnavailable)) throw error; return { response: out(503, { error: error.message, code: 'billing-unavailable' }, { 'Retry-After': '60', 'Cache-Control': 'no-store' }) }; } if (waiting) { const at = baseDomain ? `${scheme}//${String(waiting).toLowerCase()}${hostSuffix}/billing` : `/w/${encodeURIComponent(waiting)}/billing`; return { response: out(402, { error: `${waiting} is still waiting on a plan. Subscribe on its billing page before starting another workspace.`, code: 'plan-required', waiting, billing: at }) }; } } return { found, me }; }; const heldStart = async () => { prune(starts); const key = cookies.wv_start ? await digest(cookies.wv_start) : null; const held = key ? starts.get(key) : null; return held ? { key, token: cookies.wv_start, logins: held.logins } : null; }; const holdLogin = async (who) => { const held = await heldStart(); const login = { issuer: who.issuer, subject: who.subject, email: who.email ?? null, ...(who.name ? { name: who.name } : {}) }; const logins = (held?.logins ?? []).map((l) => (sameLogin(l, login) ? login : l)); if (!logins.some((l) => sameLogin(l, login))) logins.push(login); const token = held?.token ?? newChallenge(); starts.set(held?.key ?? await digest(token), { logins, expiresAt: Date.now() + START_TTL_MS }); return { token, logins }; }; const railDomain = baseDomain ? `; Domain=.${baseDomain}` : ''; const railCookie = (names) => `wv_rail=${names.join(',')}; HttpOnly; SameSite=Lax; Path=/${railDomain}; Max-Age=${Weave.SESSION_TTL_MS / 1000}${secureFlag(rx)}`; const clearRail = () => `wv_rail=; HttpOnly; SameSite=Lax; Path=/${railDomain}; Max-Age=0${secureFlag(rx)}`; const recent = String(cookies.wv_recent ?? '').toLowerCase().split(',').filter((n) => RECENT_NAME.test(n)); const stampRecent = (hit) => { const name = hub.entries().find(([, w]) => w === weave)?.[0]?.toLowerCase(); if (hit && rx.method === 'GET' && hit.status < 400 && RECENT_NAME.test(name ?? '')) { const names = [...new Set([name, ...recent])].slice(0, RECENT_CAP); jar.push(`wv_recent=${names.join(',')}; HttpOnly; SameSite=Lax; Path=/${railDomain}; Max-Age=${Weave.SESSION_TTL_MS / 1000}${secureFlag(rx)}`); } return hit; }; const startCookies = ({ token, logins }) => [`wv_start=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${START_TTL_MS / 1000}${secureFlag(rx)}`, railCookie(findWorkspaces(logins).names)]; const withCookies = (res, more) => ({ ...res, headers: { ...res.headers, 'Set-Cookie': [res.headers['Set-Cookie'] ?? []].flat().concat(more) } }); if (path === '/' && baseDomain && oidc && !hostOrigin && !wsPrefix && ['GET', 'HEAD'].includes(rx.method)) { const railed = (await heldStart()) ? null : String(cookies.wv_rail ?? '').split(',').filter(Boolean).map((n) => hub.bySlug(n)).find((w) => w && !w.state.meta.deletedAt && !isDocsWorkspace(w)); return { status: 302, headers: { Location: railed ? `${scheme}//${String(railed.state.meta.name).toLowerCase()}${hostSuffix}/#/resume` : '/start', 'Cache-Control': 'no-store' }, body: '' }; } const resolved = new Map(); const ownSession = (w) => { if (resolved.has(w)) return resolved.get(w); const name = cookieName(w); const own = cookies[name] ? w.verifySession(cookies[name]) : null; const hit = own ? { ...own, engine: w, cookie: name } : null; if (hit?.renewed) jar.push(sessionCookie(name, cookies[name], rx)); resolved.set(w, hit); return hit; }; const docsVisitor = (w) => { if (!isDocsWorkspace(w) || !w.state.meta.requireAuth) return null; for (const name of Object.keys(cookies)) { const from = name.startsWith(`${LEGACY_COOKIE}_`) ? hub.get(name.slice(LEGACY_COOKIE.length + 1)) : null; const hit = from && from !== w ? ownSession(from) : null; if (hit) return { ...hit, role: 'observer', visitor: true }; } return null; }; const docsToken = (w, token) => { if (!isDocsWorkspace(w) || !w.state.meta.requireAuth) return null; const from = hub.entries().find(([, x]) => x !== w && !x.state.meta.deletedAt && x.verifyToken(token))?.[1]; return from ? { ...from.verifyToken(token), role: 'observer', visitor: true } : null; }; const sessionOn = (w) => ownSession(w) ?? docsVisitor(w); const authz = rx.header('authorization'); let oauth = null; let upload = null; const uploadToken = /^Bearer (wvu_[A-Za-z0-9_-]+)$/i.exec(authz ?? '')?.[1]; if (uploadToken) { prune(uploads); prune(spentUploads); upload = uploads.get(uploadToken); const spent = upload ? null : spentUploads.get(uploadToken); if (spent?.incomplete && path === '/api/uploads' && rx.method === 'PUT') { return out(400, { error: 'The upload ended before all of its bytes arrived; prepare it again and send the whole file', code: 'incomplete-upload' }, { 'Cache-Control': 'no-store' }); } if (!upload || upload.weave !== weave || upload.mount !== wsPrefix || upload.host !== String(rx.header('host')).toLowerCase() || path !== '/api/uploads' || rx.method !== 'PUT') return deny(401, 'Invalid or expired upload grant; prepare the upload again over MCP'); uploads.delete(uploadToken); spentUploads.set(uploadToken, { expiresAt: upload.expiresAt, landed: false, incomplete: false }); upload.engine.maybeRefresh(); if (upload.accountId == null) { if (upload.engine.listAccounts().length) return deny(401, 'This workspace now has accounts; prepare the upload again'); } else { const account = upload.engine.listAccounts().find((a) => a.id === upload.accountId); if (!account || (upload.identity && upload.engine.accountForIdentity(upload.identity, { touch: false })?.id !== account.id)) return deny(401, 'The upload account or sign-in link no longer exists'); if (upload.engine !== weave && Weave.roleName(account.role) !== 'architect') return deny(403, 'The upload account no longer administers this workspace'); oauth = { account, engine: upload.engine }; } } let mcpDoor = false; if (path.startsWith('/.well-known/oauth-protected-resource') && !wsPrefix) { const rest = path.slice('/.well-known/oauth-protected-resource'.length); const wm = rest.match(/^\/w\/([^/]+)\/mcp$/); if (!oidc || rx.method !== 'GET' || !(rest === '' || rest === '/mcp' || (wm && hub.get(wm[1])))) { return notFound({ error: oidc ? 'No such protected resource' : 'No identity provider is configured (WEAVE_OIDC_ISSUER)', code: 'not-found' }); } return out(200, { resource: (hostOrigin ?? mcpOrigin(rx)) + rest, authorization_servers: [oidc.issuer], scopes_supported: ['profile', 'offline_access'], bearer_methods_supported: ['header'], }); } if (path === '/.well-known/openai-apps-challenge' && !wsPrefix && ['GET', 'HEAD'].includes(rx.method)) { const token = process.env.WEAVE_OPENAI_APPS_CHALLENGE?.trim(); if (!token) return notFound({ error: 'No apps challenge is configured', code: 'not-found' }); return out(200, token, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' }); } if (path === '/mcp') { if (oidc) mcpChallenge = `Bearer resource_metadata="${hostOrigin ?? mcpOrigin(rx)}/.well-known/oauth-protected-resource${wsPrefix}/mcp"`; const bearer = authz && /^Bearer /i.test(authz) ? authz.slice(7).trim() : ''; if (oidc && !bearer && ['GET', 'HEAD'].includes(rx.method) && prefersHtml(rx.header('accept'))) { const workspace = weave.state.meta.name; return out(401, renderMcpDoorPage({ workspace, url: `${hostOrigin ?? mcpOrigin(rx)}${wsPrefix}/mcp`, server: workspaceSlug(workspace) || 'weave', docsHref: `${wsPrefix}/docs`, signInHref: `${wsPrefix}/auth`, signUpHref: openSignup.on ? `${startRoot}/start?mode=signup` : null }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', 'WWW-Authenticate': mcpChallenge }); } if (oidc && !bearer) return deny(401, `Sign in with ${oidc.name} to use this MCP server`); if (oidc && !bearer.startsWith('wv_')) { const ip = clientIp(rx); if (limited('failed', ip, { peek: true })) return out(429, { error: 'Too many refused tokens: wait a minute and try again', code: 'rate-limited' }); let who; try { who = await oidc.identify(bearer); } catch (err) { if (err instanceof WeaveError) { noteFailure(ip); return deny(401, err.message); } return out(502, { error: `${oidc.name} is not answering: ${err.message}`, code: 'bad-gateway' }); } const account = weave.accountForIdentity(who); const engine = account ? weave : null; if (!engine) return deny(403, `This ${oidc.name} sign-in opens no account on this workspace. Ask an operator for an invite (weave account link ) and open it in a browser once; then sign in here again.`); oauth = { account, engine, identity: who }; } path = '/api/mcp'; mcpDoor = true; } const door = shareDoor(path, authz); let share = door ? weave.shareByToken(door.token) : null; if (door) { if (!share) return door.page ? notFound({ error: 'This share link is not (or no longer) valid', code: 'not-found' }) : deny(401, 'Invalid token'); if (share.visibility === 'private' && !sessionOn(weave)) return door.page ? wallPage() : deny(401, 'This share link is private: sign in to open it'); weave.actor = actor = `share:${share.label || share.id.slice(0, 8)}`.slice(0, 120); if (share.mode === 'submit') { if (door.rest === '' || door.rest === '/') path = `/f/${share.formId}`; else if (door.rest.startsWith('/api/')) path = door.rest; else return notFound({ error: 'Not found', code: 'not-found' }); if (!((['GET', 'HEAD'].includes(rx.method) && path === `/f/${share.formId}`) || (rx.method === 'POST' && path === `/api/forms/${share.formId}/submit`))) return deny(403, 'A submission link only accepts its form'); } else if (door.page) { const hit = sharePage(weave, share, door.rest, { method: rx.method, prefix: wsPrefix, searchParams: rx.searchParams }); if (hit.redirect) return { status: 302, headers: { Location: hit.redirect, 'Cache-Control': 'no-store' }, body: '' }; if (hit.html) return out(200, hit.html, sandboxedHtmlHeaders({ 'Cache-Control': 'no-store' }, HTML_PAGE_POLICY)); if (hit.status === 405) return out(405, { error: 'A share page is read-only', code: 'method-not-allowed' }, { Allow: 'GET, HEAD' }); if (!hit.path) return notFound({ error: 'Not found', code: 'not-found' }); path = hit.path; } else if (door.rest.startsWith('/api/')) path = door.rest; } if (share) role = null; else if (authz && /^Bearer /i.test(authz) && !(upload && !oauth)) { const account = oauth?.account ?? weave.verifyToken(authz.slice(7).trim()) ?? docsToken(weave, authz.slice(7).trim()); if (!account) return path.startsWith('/api/') ? deny(401, 'Invalid token') : wallPage(); weave.actor = actor = oauth ? `${accountActor(account.id)} via MCP` : actorOf(account); accountName = account.name; requestAccount = account; role = Weave.roleName(account.role); } else if ((session = sessionOn(weave))) { weave.actor = actor = actorOf(session); accountName = session.name; requestAccount = session; role = Weave.roleName(session.role); } else if (weave.state.meta.requireAuth && !openDoor && cookies[cookieName(weave)]) { if (weave.removedSession(cookies[cookieName(weave)])) { const ws = weave.state.meta.name; if (path.startsWith('/api/')) return out(403, { error: `Your access to ${ws} was removed`, code: 'access-removed' }, { 'Cache-Control': 'no-store' }); return out(403, renderRefusalPage({ title: `Your access to ${ws} was removed`, lines: [`A workspace architect removed your account from ${ws}, so this browser's session there has ended.`, 'Ask an architect for a new invite link, open one of your other workspaces, or sign in with a different account.'], actions: [{ href: `${startRoot}/start?pick=1`, label: 'Find your workspaces' }, { href: `${wsPrefix}/auth?signed-out=1`, label: 'Sign in with a different account' }], }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } return path.startsWith('/api/') ? deny(401, 'Session expired or revoked') : { status: 302, headers: { Location: authHref, 'Set-Cookie': clearCookie(cookieName(weave), rx), 'Cache-Control': 'no-store' }, body: '' }; } else if (weave.state.meta.requireAuth && !openDoor) { if (path.startsWith('/api/')) return deny(401, 'This workspace requires authentication'); const preview = weave.state.meta.linkPreview && ['GET', 'HEAD'].includes(rx.method) ? linkPreview(path, { signedOut: true }) : null; if (preview) return out(200, previewPageHtml(preview.head, authHref), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); return wall(); } const localAccount = (w, a) => !!a && Object.hasOwn(w.state.meta.accounts ?? {}, a.accountId ?? a.id); const viewerAs = (w, a) => (localAccount(w, a) ? viewerFor(a) : { accountId: null, role: Weave.roleName(a.role) ?? 'observer' }); let viewer = share ? { share } : role ? viewerAs(weave, { ...requestAccount, role }) : ANONYMOUS; let unseated = null; const seatRequired = () => { const ws = weave.state.meta.name; const ended = weave.state.meta.trialFrom && billing?.trialDays ? `The free trial of ${ws} has ended, so creating and editing there needs a paid seat.` : `Creating and editing in ${ws} needs a paid seat.`; return unseated === 'architect' ? `${ended} Reading and commenting stay free. Subscribe on the billing page to keep editing.` : `${ended} Reading and commenting stay free. Ask an architect of ${ws} to subscribe on the billing page.`; }; if (!startDoor && billing && PAID_ROLES.has(role ? topRole(weave, viewer) : null) && accountName && !['GET', 'HEAD', 'OPTIONS'].includes(rx.method) && !path.startsWith('/api/billing') && !path.startsWith('/api/auth/')) { try { if (!(await billing.standing({ workspace: weave.billing(), accounts: weave.listAccounts() })).seated(accountName)) { unseated = topRole(weave, viewer); role = 'observer'; viewer = { ...viewer, unseated: true }; } } catch (error) { if (!(error instanceof BillingUnavailable)) throw error; return out(503, { error: error.message, code: 'billing-unavailable' }, { 'Retry-After': '60', 'Cache-Control': 'no-store' }); } } const guarded = () => !!weave.listAccounts().length; if (role) role = workspaceRole(weave, viewer); const viewerOn = (w) => { if (!w.listAccounts().length && !w.state.meta.requireAuth) return null; if (w === weave) return viewer; if (share) return ANONYMOUS; const bearer = authz && /^Bearer /i.test(authz) ? authz.slice(7).trim() : null; const a = oauth ? (w === oauth.engine ? oauth.account : null) : bearer ? w.verifyToken(bearer) ?? docsToken(w, bearer) : sessionOn(w); return a ? viewerAs(w, a) : ANONYMOUS; }; const seen = new Map(); viewers.of = (w) => { if (!seen.has(w)) seen.set(w, viewerOn(w)); return seen.get(w); }; const pathSpace = () => unrestricted(() => { try { let m2; if ((m2 = path.match(/^\/api\/tables\/([^/]+)/))) return weave.findTable(decodeURIComponent(m2[1]))?.spaceId ?? null; if ((m2 = path.match(/^\/api\/spaces\/([^/]+)/))) return weave.findSpace(decodeURIComponent(m2[1]))?.id ?? null; if ((m2 = path.match(/^\/api\/entities\/([^/]+)/))) return weave.findTable(weave.getEntity(decodeURIComponent(m2[1])).dbId)?.spaceId ?? null; } catch {} return null; }); const targetSpace = role && !share ? pathSpace() : null; if (requestAccount?.space && localAccount(weave, requestAccount)) viewers.tokenWrite = { engine: weave, accountId: requestAccount.accountId ?? requestAccount.id }; const actorForRequest = role || share || !guarded() ? actor : 'anonymous'; weave.actor = actorForRequest; const requestActor = role ? actorForRequest : 'anonymous'; const targetHidden = !!targetSpace && !weave.canSee(targetSpace); const bulkRows = !!role && rx.method === 'POST' && path === '/api/bulk' && topRole(weave, viewer) !== 'observer'; const cap = startDoor || targetHidden ? null : targetSpace ? spaceRole(weave, viewer, weave.state.spaces[targetSpace]) : bulkRows ? 'editor' : role ?? (!share && !weave.state.meta.requireAuth && guarded() ? 'observer' : null); if (cap && cap !== 'architect') { const m2 = rx.method; const hit = matchRoute(m2, path); const read = m2 === 'GET' || m2 === 'HEAD' || path.startsWith('/api/auth/') || (m2 === 'POST' && path === '/api/mcp' && !!role) || (m2 === 'POST' && path === '/api/markdown') || hit?.cmd.writes === 'none'; const schemaWrite = !read && (hit ? hit.cmd.writes === 'schema' : path === '/api/onboarding'); const sysM = !read && (path.match(/^\/api\/tables\/([^/]+)\/(?:entities|import\.csv)/) ?? path.match(/^\/api\/entities\/([^/]+)/)); const sysTouch = sysM && (() => { try { const ref = sysM[0].startsWith('/api/entities/') ? weave.state.tables[weave.getEntity(sysM[1]).dbId] : weave.findTable(decodeURIComponent(sysM[1])); return !!ref?.system; } catch { return false; } })(); const observer = cap !== 'editor'; const cm = observer && !read && path.match(/^\/api\/entities\/([^/]+)\/comments(?:\/([^/]+?))?$/); const ownComment = cm && (cm[2] == null ? m2 === 'POST' : m2 === 'DELETE' && (() => { try { return weave.isActor(weave.getEntity(cm[1]).comments.find((c) => c.id === cm[2])?.author); } catch { return false; } })()); const submits = m2 === 'POST' && (path === '/api/bug-report' || path === '/api/workspace/leave' || /^\/api\/forms\/[^/]+\/submit$/.test(path)); if (observer && !read && !ownComment && !submits) { if (unseated) return out(402, { error: seatRequired(), code: 'seat-required', billing: `${wsPrefix}/billing` }, { 'Cache-Control': 'no-store' }); return role ? deny(403, 'An observer may read and comment, nothing else') : deny(401, 'This workspace has accounts: send a token or sign in to write'); } if (cap === 'editor' && (schemaWrite || sysTouch)) return deny(403, 'This token cannot change the schema'); } const accountOn = (w) => { if (oauth) return w === oauth.engine ? oauth.account : null; if (authz && /^Bearer /i.test(authz)) return w.verifyToken(authz.slice(7).trim()) ?? docsToken(w, authz.slice(7).trim()); return sessionOn(w); }; const roleOn = (w) => { if (w === weave) return role; const account = accountOn(w); return account ? workspaceRole(w, viewerAs(w, account)) : null; }; const actorOn = (w) => w === weave ? actorForRequest : actorOf(accountOn(w)) ?? `from ${weave.state.meta.name}: ${weave.actorName(actorForRequest)}`; const plainMention = (kind, ref) => { try { if (kind === 'workspace') { return { href: `${wsPrefix}/`, label: weave.state.meta.name || 'workspace' }; } if (kind === 'space') { const sp = weave.findSpace(ref); return sp ? { href: `${wsPrefix}/#/space/${sp.id}`, label: sp.name } : null; } if (kind === 'table') { const db = weave.findTable(ref); return db ? { href: `${wsPrefix}/#/table/${db.id}`, label: weave.qualifiedName(db) } : null; } if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(ref)) { const e = weave.state.entities[ref]; if (!e || e.deletedAt || !weave.canSee(e.id)) return null; return { href: `${wsPrefix}/e/${e.id}/doc.html`, label: weave.entityName(e), name: weave.entityName(e), home: weave.state.tables[e.dbId]?.name, view: weave.renderView(e.id, 'chip'), fields: weave.previewFields(e.id), }; } const m = /^(.+)#(\d+)$/.exec(ref); if (!m) return null; const db = weave.findTable(m[1].trim()); if (!db) return null; const entity = weave.listEntities(db.id).find((e) => String(e.publicId) === m[2]); if (!entity) return null; return { href: `${wsPrefix}/e/${entity.id}/doc.html`, label: `${db.name}#${m[2]} — ${weave.entityName(entity)}`, name: weave.entityName(entity), home: db.name, view: weave.renderView(entity.id, 'chip'), fields: weave.previewFields(entity.id), }; } catch { return null; } }; const resolveMention = share ? scopedMention(weave, share, plainMention, `${wsPrefix}/s/${share.token}`) : plainMention; if (rx.method === 'OPTIONS') { return { status: 204, headers: { Allow: 'GET,POST,PUT,PATCH,DELETE,OPTIONS' }, body: '' }; } try { let m; if ((m = path.match(/^\/e\/([^/]+)\/doc(?:\/([^/]+?))?\.(md|mmd|html|pdf)$/))) { const entity = sharedRead(weave, share, m[1]); const fieldRef = m[2] ?? null; const markdown = weave.getDoc(m[1], fieldRef); const docLabel = fieldRef ? ` • ${fieldRef}` : ''; const subtitle = `${entity.db} #${entity.publicId} • ${entity.name}${docLabel} • updated ${entity.updatedAt.slice(0, 10)}`; if (m[3] === 'md') { return out(200, markdown, { 'Content-Type': 'text/markdown; charset=utf-8' }); } if (m[3] === 'mmd') { return out(200, markdown, { 'Content-Type': 'text/vnd.mermaid; charset=utf-8' }); } if (m[3] === 'html') { if (isHtmlDocument(markdown)) return out(200, rx.searchParams.get('inline') === '1' ? viewHtml(Buffer.from(markdown), frameLinkBase(Buffer.from(markdown)) + FRAME_HEIGHT_SCRIPT) : markdown, sandboxedHtmlHeaders()); const page = renderDocumentPage({ title: entity.name || `#${entity.publicId}`, subtitle, markdown, resolveMention }); return out(200, page, sandboxedHtmlHeaders({}, HTML_PAGE_POLICY)); } const pdf = markdownToPdf(markdown, { title: entity.name || `#${entity.publicId}`, subtitle }); return out(200, pdf, fileHeaders({ name: `${entity.name || 'document'}.pdf`, mime: 'application/pdf' }, pdf)); } if ((m = path.match(/^\/e\/([^/]+)\/entity\.(md|mmd|html|pdf)$/))) { const entity = sharedRead(weave, share, m[1]); const lines = [`# ${entity.name || '#' + entity.publicId}`, '', `${entity.db} #${entity.publicId} • updated ${entity.updatedAt.slice(0, 10)}`, '', '## Fields', '', '| Field | Value |', '|---|---|']; for (const [k, v] of Object.entries(entity.fields)) { if (k in entity.docs) continue; const val = v == null ? '' : Array.isArray(v) ? v.map((x) => (x && typeof x === 'object' ? x.name : x)).join(', ') : typeof v === 'object' ? (v.name ?? '') : String(v); lines.push(`| ${k} | ${String(val).replace(/\|/g, '\\|')} |`); } for (const [docName, docText] of Object.entries(entity.docs)) { lines.push('', '
', '', `## ${docName}`, '', docText || '_empty_'); } if (entity.notice) lines.push('', entity.notice); const md = lines.join('\n'); const subtitle = `${entity.db} #${entity.publicId} • full entity export`; if (m[2] === 'md') return out(200, md, { 'Content-Type': 'text/markdown; charset=utf-8' }); if (m[2] === 'mmd') return out(200, md, { 'Content-Type': 'text/vnd.mermaid; charset=utf-8' }); if (m[2] === 'html') { return out(200, renderDocumentPage({ title: entity.name || `#${entity.publicId}`, subtitle, markdown: md, resolveMention }), sandboxedHtmlHeaders({}, HTML_PAGE_POLICY)); } const pdf = markdownToPdf(md, { title: entity.name || `#${entity.publicId}`, subtitle }); return out(200, pdf, fileHeaders({ name: `${entity.name || 'entity'}.pdf`, mime: 'application/pdf' }, pdf)); } if ((m = path.match(/^\/e\/([^/]+)\/deck\.(html|json)$/))) { const { renderDeck } = await deckModule(); const built = renderDeck(weave, m[1], share ? { admit: (id) => withinScope(weave, share, { entityId: id }) } : {}); if (m[2] === 'json') { return out(200, { model: built.model, errors: built.errors, warnings: built.warnings }); } return out(200, built.html, sandboxedHtmlHeaders({ 'X-Weave-Deck-Warnings': String(built.warnings.length) })); } if ((m = path.match(/^\/t\/([^/]+)\/new$/)) && ['GET', 'HEAD'].includes(rx.method)) { const preview = linkPreview(`/t/${m[1]}`); if (!preview) throw new WeaveError(`Nothing at ${wsPrefix}${path}`, 'not-found'); const q = weavePrefill.clip(rx.searchParams).query; const to = `${wsPrefix}/#/table/${m[1]}/new${q ? `?${q}` : ''}`; if (!serveStatic) return { status: 302, headers: { Location: to, 'Cache-Control': 'no-store' }, body: '' }; const hit = serveStatic('/', rx, { head: `${preview.head}${shellMarker()}\n\n` }); if (hit) return stampRecent({ ...hit, headers: { ...hit.headers, 'Cache-Control': 'no-store' } }); } if ((m = path.match(/^\/(e|s|t)\/([^/]+)$/)) && !['GET', 'HEAD'].includes(rx.method)) { throw new WeaveError(`A permalink answers GET, not ${rx.method}`, 'not-found'); } if (['GET', 'HEAD'].includes(rx.method) && (path === '/' || m)) { const preview = linkPreview(path); if (!preview && path !== '/') throw new WeaveError(`Nothing at ${wsPrefix}${path}`, 'not-found'); if (preview?.route && !serveStatic) return { status: 302, headers: { Location: preview.route }, body: '' }; const jump = preview?.route ? `\n\n` : ''; const hit = preview && serveStatic ? serveStatic('/', rx, { head: preview.head + shellMarker() + jump }) : null; if (hit) return m?.[1] === 's' ? hit : stampRecent(hit); } const formClient = () => { const current = cookies.wv_form_client; if (/^[A-Za-z0-9_-]{43}$/.test(current ?? '')) return current; const token = newChallenge(); jar.push(`wv_form_client=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=2592000${secureFlag(rx)}`); return token; }; const formFor = (ref) => { let form; try { form = getForm(weave, decodeURIComponent(ref)); } catch (err) { if (err instanceof WeaveError && err.code === 'not-found' && weave.state.meta.requireAuth && role == null) return { form: null, admitted: false }; throw err; } if (share) return { form, admitted: share.mode === 'submit' && form.enabled && share.formId === form.id && form.shareOnly && form.owner === weave && share.scope.id === form.tableId }; return { form, admitted: formAdmits(form, { role: roleOn(form.owner), anonymous: anonymousForms }) }; }; if ((m = path.match(/^\/f\/([^/]+)$/)) && ['GET', 'HEAD'].includes(rx.method)) { const { form, admitted } = formFor(m[1]); if (!admitted) return wall(); formClient(); const formParams = new URLSearchParams(rx.searchParams); if (share) formParams.delete('preview'); return out(200, renderFormPage(form, { mount: share ? `${wsPrefix}/s/${share.token}` : wsPrefix, ...(share && rx.searchParams.get('preview') === '1' ? { submitUrl: `${wsPrefix}/s/${share.token}/api/forms/${form.id}/submit?preview=1` } : {}), searchParams: formParams }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } if ((m = path.match(/^\/api\/forms\/([^/]+)\/submit$/)) && rx.method === 'POST') { if (share && rx.searchParams.get('preview') === '1') return deny(403, 'Preview does not submit entries'); const { form, admitted } = formFor(m[1]); if (!admitted) return deny(401, 'Sign in to submit this form'); const ip = clientIp(rx); const key = `${form.owner.state.meta.id}:${form.id}`; const retry = formLimited([['server', limits.formServer ?? 1000], [`form:${key}`, limits.formTotal ?? 120], [`sender:${key}:${ip}`, limits.formSender ?? 20]]); if (retry) return out(429, { error: 'Too many submissions. Try again later.', code: 'rate-limited' }, { 'Retry-After': String(retry) }); const signedIn = roleOn(form.owner) != null; const actor = !signedIn ? 'anonymous' : form.owner === weave ? requestActor : actorOf((authz && /^Bearer /i.test(authz) ? form.owner.verifyToken(authz.slice(7).trim()) ?? docsToken(form.owner, authz.slice(7).trim()) : sessionOn(form.owner)) ?? null) ?? requestActor; const body = await rx.readBody(); const idempotencyKey = rx.header('idempotency-key') ?? body?.idempotencyKey; const client = formClient(); if (!signedIn && idempotencyKey != null && !/^[A-Za-z0-9_-]{32,200}$/.test(idempotencyKey)) throw new WeaveError('Anonymous Idempotency-Key must contain 32 to 200 letters, digits, hyphens or underscores', 'invalid'); const input = form.kind === 'Row' && body && typeof body === 'object' && !Array.isArray(body) && !Object.hasOwn(body, 'values') ? { values: Object.fromEntries(Object.entries(body).filter(([key]) => key !== 'idempotencyKey')) } : body; const result = submitForm(weave, form.id, input ?? {}, { actor, ...(share ? { authorize: freshForm => { const current = weave.shareByToken(door.token); if (!current || current.mode !== 'submit' || current.formId !== freshForm.id || current.scope.id !== freshForm.tableId || !freshForm.shareOnly || freshForm.owner !== weave) throw new WeaveError('This submission link is no longer valid', 'forbidden'); } } : {}), server: { version, startedAt: STARTED_AT, uptime: Math.round(uptime()), workspace: weave.state.meta.name }, idempotencyKey, scope: `${share ? `share:${share.token}:` : ''}${signedIn ? '' : idempotencyKey != null ? `key:${await digest(idempotencyKey)}` : `client:${client}`}`, dedupScope: `${share ? `share:${share.token}:` : ''}${signedIn ? '' : `client:${client}`}`, }); return out(result.replayed ? 200 : 201, share ? { submitted: true, ...(result.replayed ? { replayed: true } : {}), ...(result.duplicate ? { duplicate: true } : {}) } : result); } if (statusPage) { const page = serveStatic?.('/status.html', rx); return page ?? notFound({ error: 'Not found', code: 'not-found' }); } if (path === '/auth') { const startDoorAt = (next) => { const q = new URLSearchParams(); if (next) q.set('next', next); if (rx.searchParams?.get('mode') === 'signup') q.set('mode', 'signup'); return { status: 302, headers: { Location: `${hostOrigin ? apexOrigin : ''}/start${q.size ? `?${q}` : ''}`, 'Cache-Control': 'no-store' }, body: '' }; }; const hasDoor = oidc && (baseDomain || clerkPane); if (hasDoor && !hostOrigin && !wsPrefix) { const asked = rx.searchParams?.get('next'); return startDoorAt(startReturn(asked) ? asked : null); } if (oidc && !session && !role && !rx.searchParams?.has('signed-out')) { const q = rx.searchParams?.toString() ?? ''; if (!origin && String(rx.header('host') ?? '').split(':')[0] === '127.0.0.1') { return { status: 302, headers: { Location: `${originFor(rx)}${wsPrefix}/auth${q ? '?' + q : ''}`, 'Cache-Control': 'no-store' }, body: '' }; } const n = sameOriginPath(rx.searchParams?.get('next'), originFor(rx), null); if (clerkPane && hostOrigin) { const deep = hostPath(rx.searchParams?.get('next')); if (isDocsWorkspace(weave)) return startDoorAt(`/w/${encodeURIComponent(hostSlug)}${deep ?? '/'}`); return startDoorAt(`/api/start/open/${encodeURIComponent(hostSlug)}${deep && deep !== '/' ? `?path=${encodeURIComponent(deep)}` : ''}`); } if (hasDoor && wsPrefix && !hostOrigin) return startDoorAt(n?.startsWith(`${wsPrefix}/`) ? n : `${wsPrefix}/`); const next = n ? `?next=${encodeURIComponent(n)}` : ''; return { status: 302, headers: { Location: `${wsPrefix}/api/auth/oidc/start${next}`, 'Cache-Control': 'no-store' }, body: '' }; } return out(200, renderAuthPage({ mount: wsPrefix, workspace: weave.state.meta.name, provider: oidc?.name ?? null, analyticsKey: posthog?.key ?? null, signInPath: clerkPane ? `${wsPrefix}/auth` : null, signUpHref: oidc && openSignup.on ? `${startRoot}/start?mode=signup` : null }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } if (path === '/start' && ['GET', 'HEAD'].includes(rx.method)) { if (hostOrigin && oidc) { const q = rx.searchParams?.toString(); return { status: 302, headers: { Location: `${apexOrigin}/start${q ? `?${q}` : ''}`, 'Cache-Control': 'no-store' }, body: '' }; } const asked = ['pick', 'setup', 'error', 'mode', 'next', 'redirect_url'].some((k) => rx.searchParams?.has(k)); const logins = !hostOrigin && !wsPrefix && oidc && !asked ? (await heldStart())?.logins ?? [] : []; const found = logins.length ? findWorkspaces(logins) : null; const last = found?.rows.find((r) => r.kind === 'workspace' && r.name.toLowerCase() === found.names[0]) ?? found?.rows.find((r) => r.kind === 'workspace'); if (last && !found.rows.some((r) => r.kind === 'invite')) return { status: 302, headers: { Location: `${last.open}?to=resume`, 'Cache-Control': 'no-store' }, body: '' }; const page = !hostOrigin && !wsPrefix && oidc && serveStatic ? serveStatic('/start.html', rx) : null; const back = rx.searchParams?.get('next'); const workspaceReturn = back == null || startReturn(back); const sentBack = rx.searchParams?.get('redirect_url'); const providerReturn = sentBack == null || isProviderReturn(sentBack); if (page && !(workspaceReturn && providerReturn)) { const q = new URLSearchParams(rx.searchParams); if (!workspaceReturn) q.delete('next'); if (!providerReturn) q.delete('redirect_url'); return { status: 302, headers: { Location: `/start${q.size ? `?${q}` : ''}`, 'Cache-Control': 'no-store' }, body: '' }; } if (page) return { ...page, headers: { ...page.headers, 'Cache-Control': 'no-store' } }; return notFound({ error: 'Not found', code: 'not-found' }); } if (path === '/billing' && ['GET', 'HEAD'].includes(rx.method)) { const page = billing && serveStatic ? serveStatic('/billing.html', rx) : null; if (page) return { ...page, headers: { ...page.headers, 'Cache-Control': 'no-store' } }; return notFound({ error: 'Not found', code: 'not-found' }); } if (legalPage) { const [title, markdown] = { '/privacy': ['Privacy policy', PRIVACY], '/terms': ['Terms of Service', TERMS], '/support': ['Support', SUPPORT], '/docs': ['Connect an agent', `${AGENT_DOCS.replaceAll('{{MCP_URL}}', `${hostOrigin ?? mcpOrigin(rx)}${wsPrefix}/mcp`).replace('{{ACCOUNT}}', oidc ? `[Sign in](${wsPrefix}/auth)${openSignup.on ? ` or [Create an account](${startRoot}/start?mode=signup)` : ''} to open this workspace in a browser.\n\n` : '')}\n\n## Tools\n\n${toolTable()}\n`], }[path]; const html = renderDocumentPage({ title, subtitle: 'weave', markdown }); return out(200, path === '/privacy' ? html.replace('

Cookies

', '

Cookies

') : html, { 'Content-Type': 'text/html; charset=utf-8' }); } if (path === '/api/backups' || (m = path.match(/^\/api\/backups\/([^/]+)$/))) { const archive = path === '/api/backups' ? null : m[1]; const bearer = authz && /^Bearer /i.test(authz) ? authz.slice(7).trim() : ''; const account = bearer.startsWith('wv_') ? weave.verifyToken(bearer) : null; if (!account) return deny(401, 'Backups need the account token: send Authorization: Bearer wv_…'); const claimed = rx.header('x-weave-account'); let named = claimed; try { named = claimed == null ? null : decodeURIComponent(claimed); } catch {} if (named != null && named !== account.name) return deny(403, `This token belongs to the account ${account.name}, not ${named}`); if (!cloudBackups) return out(400, { error: 'This deployment keeps no backups: it has no bucket configured.', code: 'invalid' }); if (rx.method === 'GET' && !archive) { const backups = await cloudBackups.list(account.id); return out(200, { account: account.name, quota: cloudBackups.quota, used: backups.reduce((n, b) => n + b.bytes, 0), backups }); } if (rx.method === 'GET') { const bytes = await cloudBackups.get(account.id, archive); return out(200, new Uint8Array(bytes), { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${archive}"`, 'Cache-Control': 'no-store' }); } if (rx.method === 'PUT' && archive) { if (!rx.readBytes) return out(400, { error: 'This deployment cannot take an archive upload.', code: 'invalid' }); return out(201, await cloudBackups.put(account.id, archive, await rx.readBytes(cloudBackups.quota))); } return out(405, { error: 'Backups answer GET /api/backups, GET /api/backups/ and PUT /api/backups/', code: 'method-not-allowed' }, { Allow: 'GET, PUT' }); } if (path === '/api/uploads' && upload) { if (guarded() && !['editor', 'architect'].includes(role)) return deny(403, 'Uploading needs an editor account'); const entity = weave.getEntity(upload.entity); if (entity.deletedAt) throw new WeaveError('The upload destination is deleted', 'not-found'); if (role === 'editor' && weave.state.tables[entity.dbId]?.system) return deny(403, 'This token cannot change the schema'); if (!rx.readBytes && !(rx.bodyStream && weave.filesOnDisk())) throw new WeaveError('This server cannot receive raw uploads', 'invalid'); if (Number(rx.header('content-length')) > upload.size) throw new WeaveError(`Body too large: ${upload.size} bytes at most`, 'too-large'); const attempt = spentUploads.get(uploadToken); const cutShort = (err) => { if (!(err instanceof WeaveError) || !['too-large', 'invalid'].includes(err.code)) attempt.incomplete = true; return err; }; const streamed = rx.bodyStream && weave.filesOnDisk() ? await weave.streamBlob(rx.bodyStream(), upload.size).catch((err) => { throw cutShort(err); }) : null; try { const bytes = streamed ?? await rx.readBytes(upload.size).catch((err) => { throw cutShort(err); }); if ((streamed ? streamed.size : bytes.length) !== upload.size) { attempt.incomplete = true; throw new WeaveError('The upload ended before all of its bytes arrived; prepare it again and send the whole file', 'incomplete-upload'); } upload.engine.maybeRefresh(); weave.maybeRefresh(); const byAccount = upload.accountId != null; const account = byAccount ? upload.engine.listAccounts().find((a) => a.id === upload.accountId) : null; const currentRole = Weave.roleName(account?.role); const refused = byAccount ? !account || (upload.identity && upload.engine.accountForIdentity(upload.identity, { touch: false })?.id !== account.id) || !['editor', 'architect'].includes(currentRole) || (upload.engine !== weave && currentRole !== 'architect') : upload.engine.listAccounts().length > 0; if (refused) return deny(403, 'The upload account no longer has permission'); if (currentRole === 'editor' && weave.state.tables[weave.getEntity(upload.entity).dbId]?.system) return deny(403, 'This token cannot change the schema'); if (weave.getEntity(upload.entity).deletedAt) throw new WeaveError('The upload destination is deleted', 'not-found'); weave.actor = actorForRequest; const file = { name: upload.name, mime: upload.mime, preview: upload.preview, ...(streamed ? { staged: streamed } : { bytes: Buffer.from(bytes) }) }; const landed = upload.field == null ? weave.attachFile(entity.id, file) : weave.attachToField(entity.id, upload.field, file); spentUploads.get(uploadToken).landed = true; return out(201, landed, { 'Cache-Control': 'no-store' }); } finally { if (streamed) weave.dropStagedBlob(streamed.id); } } if (path.startsWith('/api/')) { const attaching = ATTACH_ROUTE.test(path); const body = ['POST', 'PUT', 'PATCH'].includes(rx.method) ? await rx.readBody(attaching ? MAX_ATTACH_BODY_BYTES : undefined).catch((err) => { if (!attaching || err?.code !== 'too-large') throw err; throw new WeaveError(`This upload is larger than ${mbText(FILE_MAX_BYTES)}, the most a file can be`, 'too-large'); }) : {}; weave.actor = actorForRequest; const prepareUpload = ({ entity, field, name, mime = 'application/octet-stream', size, contentBase64, preview = null, sameOrigin = false }) => { const guard = guarded(); if (guard && !['editor', 'architect'].includes(role)) throw new WeaveError('Preparing uploads needs an editor account', 'forbidden'); const engine = oauth?.engine ?? weave; const account = oauth?.account ?? requestAccount; if (guard && !account) throw new WeaveError('Prepare uploads through a signed-in session, OAuth or an account token', 'forbidden'); if (!Number.isSafeInteger(size) || size < 0 || size > FILE_MAX_BYTES) throw new WeaveError(`Upload size must be an integer from 0 to ${FILE_MAX_BYTES} bytes`, 'invalid'); if (contentBase64 != null || typeof name !== 'string' || !name.trim() || name.length > 255 || typeof mime !== 'string' || mime.length > 255) throw new WeaveError('Prepare with a filename, MIME type and size, without file content', 'invalid'); const row = weave.getEntity(entity); if (row.deletedAt) throw new WeaveError('The upload destination is deleted', 'not-found'); if (role === 'editor' && weave.state.tables[row.dbId]?.system) throw new WeaveError('This token cannot change the schema', 'forbidden'); const target = field == null ? null : weave.getField(row.dbId, field); if (target && target.type !== 'attachments') throw new WeaveError('The destination must be an attachments field', 'invalid'); const small = previewInput(preview); if (small && String(small.bytes).length > Math.ceil((PREVIEW_MAX_BYTES * 4) / 3) + 1024) throw new WeaveError(`A preview is at most ${PREVIEW_MAX_BYTES} bytes`, 'invalid'); prune(uploads); if (uploads.size >= 1000) throw new WeaveError('Too many pending uploads; finish one or wait ten minutes', 'rate-limited'); const token = `wvu_${newChallenge()}`; const expiresAt = Date.now() + 10 * 60 * 1000; const url = sameOrigin ? `${wsPrefix}/api/uploads` : `${hostOrigin ?? mcpOrigin(rx)}${wsPrefix}/api/uploads`; const host = sameOrigin ? String(rx.header('host') ?? '').toLowerCase() : new URL(url).host.toLowerCase(); uploads.set(token, { weave, engine, accountId: account ? account.accountId ?? account.id : null, preview: small, identity: oauth?.identity ?? null, mount: wsPrefix, host, entity: row.id, field: target?.id ?? null, name, mime, size, expiresAt }); return { url, method: 'PUT', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/octet-stream' }, maxBytes: size, expiresAt: new Date(expiresAt).toISOString() }; }; const route = `${rx.method} ${path}`; if (share) { weave.maybeRefresh(); share = weave.shareByToken(door.token); if (!share) return deny(401, 'This share link is no longer valid'); if (share.visibility === 'private' && !sessionOn(weave)) return deny(401, 'Sign in to open this share link'); const gated = shareGate(weave, share, { method: rx.method, path, body, searchParams: rx.searchParams, viewerZone }); if (gated) return out(gated.status, gated.data); } if (path.startsWith('/api/auth/')) { const ip = clientIp(rx); const tooMany = () => out(429, { error: 'Too many attempts — wait a minute and try again', code: 'rate-limited' }); const who = () => session ?? (role ? weave.verifyToken(authz.slice(7).trim()) : null); if (path.startsWith('/api/auth/oidc/')) { if (!oidc) return notFound({ error: 'No identity provider is configured (WEAVE_OIDC_ISSUER)', code: 'not-found' }); let mount = wsPrefix; const signInAgain = () => ({ href: `${mount}/auth?signed-out=1`, label: 'Back to sign in' }); const refusal = (status, title, detail, actions = [signInAgain()]) => out(status, renderRefusalPage({ title, lines: detail, actions }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); const tooMany = () => refusal(429, 'Too many sign-in attempts', 'Wait a minute, then sign in again.'); const redirectUri = `${originFor(rx)}/api/auth/oidc/callback`; if (hostOrigin && route === 'GET /api/auth/oidc/start') { const q = new URLSearchParams(rx.searchParams ?? ''); q.set('handoff', String(weave.state.meta.name).toLowerCase()); return { status: 302, headers: { Location: `${apexOrigin}/w/${encodeURIComponent(weave.state.meta.name)}/api/auth/oidc/start?${q}`, 'Cache-Control': 'no-store' }, body: '' }; } if (route === 'GET /api/auth/oidc/start') { if (limited('options', ip)) return tooMany(); const handoff = rx.searchParams?.get('handoff') || null; if (handoff && (!baseDomain || hub.bySlug(handoff) !== weave)) return refusal(400, 'This sign-in link is not valid', 'Start again from the sign-in page of the workspace you want.'); const next = sameOriginPath(rx.searchParams?.get('next'), originFor(rx), handoff ? '/' : `${wsPrefix}/`); const invite = rx.searchParams?.get('invite') || null; if (invite && !weave.identityInvite(invite)) { const member = session?.engine === weave || ((await heldStart())?.logins ?? []).some((me) => weave.accountForIdentity(me)); return refusal(410, 'This invite expired or was already used', member ? 'You already have access. Open your workspace to continue.' : 'If you already joined, sign in to open the workspace. Otherwise, ask the sender for a new invite.', [{ href: member ? `${mount}/` : `${mount}/auth`, label: member ? 'Open workspace' : 'Sign in to workspace' }, { href: '/start?pick=1', label: 'Find your workspaces' }]); } const heldLogins = invite && !rx.searchParams?.has('pick') ? (await heldStart())?.logins ?? [] : []; if (heldLogins.length) { const again = (extra) => { const u = new URLSearchParams(rx.searchParams ?? ''); for (const [k, v] of Object.entries(extra)) u.set(k, v); return `${mount}/api/auth/oidc/start?${u}`; }; const another = { label: WeaveSlugs.COPY.start.otherLogin, href: again({ fresh: '1', pick: '1' }) }; const as = rx.searchParams?.get('as'); if (as == null) { const labels = findWorkspaces(heldLogins).logins; return refusal(200, WeaveSlugs.fill(WeaveSlugs.COPY.start.chooserTitle, { workspace: weave.state.meta.name }), WeaveSlugs.COPY.start.chooserLead, [...labels.map((login, i) => ({ label: WeaveSlugs.fill(WeaveSlugs.COPY.start.acceptAs, { login }), href: again({ as: String(i) }) })), another]); } const me = heldLogins[Number(as)]; if (!me) return refusal(400, 'This choice expired', 'Open the invite link again.', [another]); let account; try { account = weave.redeemIdentityInvite(invite, me); await remirror(weave); } catch (err) { noteFailure(ip); if (err.code === 'conflict' && weave.accountForIdentity(me)) return refusal(409, `This login already opens ${weave.state.meta.name}`, 'You are already a member, so the invite was not used. Open the workspace, or accept the invite with the login it was meant for.', [{ label: 'Open workspace', href: `${startRoot}/api/start/open/${encodeURIComponent(weave.state.meta.name)}` }, another, signInAgain()]); if (err.code === 'conflict') return refusal(409, 'This login already opens another account', 'The invite was not used. Accept it with the login it was meant for.', [another, signInAgain()]); return refusal(410, 'This invite expired or was already used', 'Ask whoever sent it for a new link.'); } const kept = startCookies(await holdLogin(me)); const to = posthog ? joinedInvite(next) : next; if (handoff) return withCookies(await handOff(weave, account.id, to, handoff), kept); const minted = weave.createSession(account.id, { ua: rx.header('user-agent') }); return { status: 302, headers: { Location: to, 'Set-Cookie': [sessionCookie(cookieName(weave), minted.token, rx), ...kept], 'Cache-Control': 'no-store' }, body: '' }; } if (invite && clerkPane && !hostOrigin && !rx.searchParams?.has('pick') && !rx.searchParams?.has('fresh')) { const kept = new URLSearchParams([...rx.searchParams].filter(([k]) => INVITE_LINK_FIELDS.has(k))); return { status: 302, headers: { Location: `/start?next=${encodeURIComponent(`${wsPrefix}/api/auth/oidc/start?${kept}`)}`, 'Cache-Control': 'no-store' }, body: '' }; } let trip; try { trip = await oidc.begin({ redirectUri, fresh: rx.searchParams?.has('fresh') }); } catch (err) { return refusal(502, `${oidc.name} is not answering`, err.message); } const binder = newChallenge(); const start = !hostOrigin && !wsPrefix && (rx.searchParams?.has('start') || !!baseDomain); putChallenge({ kind: 'oidc', nonce: trip.nonce, verifier: trip.verifier, binder, next, holder: weave, invite, mount: wsPrefix, handoff, start, door: !!rx.searchParams?.has('door') }, trip.state); const secure = originFor(rx).startsWith('https:') ? '; Secure' : ''; return { status: 302, headers: { Location: trip.url, 'Set-Cookie': `wv_oidc=${binder}; HttpOnly; SameSite=Lax; Path=/api/auth/oidc; Max-Age=${CHALLENGE_TTL_MS / 1000}${secure}`, 'Cache-Control': 'no-store' }, body: '' }; } if (route === 'GET /api/auth/oidc/callback') { if (limited('failed', ip, { peek: true })) return tooMany(); const c = takeChallenge(String(rx.searchParams?.get('state') ?? ''), 'oidc'); if (!c || !cookies.wv_oidc || cookies.wv_oidc !== c.binder) { noteFailure(ip); return refusal(400, 'This sign-in expired or was already used', 'Start again from the sign-in page, in the browser you want to sign in.'); } mount = c.mount ?? ''; const toStart = (code) => ({ status: 302, headers: { Location: `/start?error=${code}`, 'Cache-Control': 'no-store' }, body: '' }); const fresh = (invite) => `${mount}/api/auth/oidc/start?fresh=1${c.handoff ? `&handoff=${encodeURIComponent(c.handoff)}` : ''}${invite ? `&invite=${encodeURIComponent(invite)}` : ''}&next=${encodeURIComponent(c.next)}`; const differentAccount = async () => ({ label: 'Use a different account', href: await oidc.endSessionUrl({ postLogoutRedirectUri: `${originFor(rx)}${mount}/auth?signed-out=1` }).catch(() => null) ?? fresh() }); const broken = (code, where) => { console.error(`weave: ALERT sign-in is broken: ${oidc.name} refused weave's own client at its ${where} (${code}); check WEAVE_OIDC_CLIENT_ID, WEAVE_OIDC_CLIENT_SECRET and the allowed scopes against the provider`); return refusal(503, 'Sign-in is broken on weave\'s side', [`${oidc.name} refused weave's own credentials (${code}), so nobody can sign in until weave's configuration is fixed.`, 'Signing in again, or with a different account, will not help. The fault is logged for the people who run weave; try again later.'], []); }; const refused = rx.searchParams.get('error'); if (CLIENT_FAULTS.has(refused)) { noteFailure(ip); return broken(refused, 'authorization endpoint'); } if (refused || !rx.searchParams.get('code')) { if (c.start) return toStart('signin_failed'); return refusal(400, `${oidc.name} did not sign you in`, String(rx.searchParams.get('error_description') ?? refused ?? 'No authorization code came back.'), [await differentAccount(), signInAgain()]); } let who; try { who = await oidc.redeem({ code: rx.searchParams.get('code'), redirectUri, verifier: c.verifier, nonce: c.nonce }); } catch (err) { noteFailure(ip); if (!(err instanceof WeaveError)) return c.start ? toStart('provider_down') : refusal(502, `${oidc.name} is not answering`, 'The identity provider could not be reached. Try again in a minute.'); if (CLIENT_FAULTS.has(err.provider)) return broken(err.provider, 'token endpoint'); return c.start ? toStart('signin_unverified') : refusal(401, `${oidc.name} sign-in could not be verified`, err.message, [await differentAccount(), signInAgain()]); } if (c.start) { const link = START_LINK.exec(c.next); let slug = link?.[1] ?? ''; try { slug = decodeURIComponent(slug); } catch {} const opens = link && hub.bySlug(slug.toLowerCase())?.accountForIdentity(who); const back = link ? (opens ? c.next : `/start?next=${encodeURIComponent(c.next)}`) : /^\/start(?:[?#]|$)/.test(c.next) || inviteLink(c.next) ? c.next : '/start'; return { status: 302, headers: { Location: back, 'Set-Cookie': startCookies(await holdLogin(who)), 'Cache-Control': 'no-store' }, body: '' }; } let account = null; let engine = null; if (c.invite) { try { account = c.holder.redeemIdentityInvite(c.invite, who); engine = c.holder; await remirror(c.holder); } catch (err) { noteFailure(ip); if (err.code === 'conflict' && c.holder.accountForIdentity(who)) return refusal(409, `This login already opens ${c.holder.state.meta.name}`, 'You are already a member, so the invite was not used. Open the workspace, or sign in as the person it was meant for to use it.', [{ label: 'Open workspace', href: `${mount}/api/auth/oidc/start?${c.handoff ? `handoff=${encodeURIComponent(c.handoff)}&` : ''}next=${encodeURIComponent(c.next)}` }, { label: 'Use a different account', href: fresh(c.invite) }, signInAgain()]); if (err.code === 'conflict') return refusal(409, 'This identity already opens another account', `You signed in at ${oidc.name} as someone this server already knows, so the invite was not used. Sign in as the person it was meant for to use it.`, [{ label: 'Use a different account', href: fresh(c.invite) }, signInAgain()]); return refusal(410, 'This invite expired or was already used', 'Ask whoever sent it for a new link.'); } } else if ((account = c.holder.accountForIdentity(who))) engine = c.holder; if (!engine && isDocsWorkspace(c.holder)) engine = hub.entries().map(([, w]) => w).find((e) => e !== c.holder && (account = e.accountForIdentity(who))); if (!engine && c.door) return { status: 302, headers: { Location: `/start?next=${encodeURIComponent(c.next)}`, 'Set-Cookie': startCookies(await holdLogin(who)), 'Cache-Control': 'no-store' }, body: '' }; if (!engine) { noteFailure(ip); const ws = c.holder.state.meta.name; const own = billing || openSignup.on ? [{ label: 'Start your own workspace', href: `${startRoot}/start` }] : []; const page = refusal(403, `No access to ${ws}`, [`You signed in at ${oidc.name}, but that account has no access to ${ws}.`, 'A workspace architect can send you an invite link that adds you. Or open one of your own workspaces, or sign in with a different account.'], [...own, { label: 'Find your workspaces', href: `${startRoot}/start?pick=1` }, await differentAccount(), signInAgain()]); return own.length ? withCookies(page, startCookies(await holdLogin(who))) : page; } const kept = startCookies(await holdLogin(who)); const to = posthog && c.invite ? joinedInvite(c.next) : c.next; if (c.handoff) return withCookies(await handOff(engine, account.id, to, c.handoff), kept); const minted = engine.createSession(account.id, { ua: rx.header('user-agent') }); const back = cookies[outName(c.holder)] ? [clearCookie(outName(c.holder), rx)] : []; return { status: 302, headers: { Location: to, 'Set-Cookie': [sessionCookie(cookieName(engine), minted.token, rx), ...back, ...kept], 'Cache-Control': 'no-store' }, body: '' }; } return notFound({ error: 'Unknown auth route', code: 'not-found' }); } if (route === 'GET /api/auth/handoff' && hostOrigin) { if (limited('failed', ip, { peek: true })) return tooMany(); const key = await digest(String(rx.searchParams?.get('code') ?? '')); const h = handoffs.get(key); handoffs.delete(key); if (!h || h.expiresAt <= Date.now() || h.host !== hostSlug) { noteFailure(ip); return out(400, renderRefusalPage({ title: 'This sign-in expired or was already used', lines: 'Start again from the sign-in page.', actions: [{ href: '/auth?signed-out=1', label: 'Back to sign in' }] }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } const minted = h.engine.createSession(h.accountId, { ua: rx.header('user-agent') }); return { status: 302, headers: { Location: h.next, 'Set-Cookie': sessionCookie(cookieName(h.engine), minted.token, rx), 'Cache-Control': 'no-store' }, body: '' }; } if ((m = path.match(/^\/api\/auth\/open\/([^/]+)$/)) && rx.method === 'GET' && baseDomain) { const target = hub.bySlug(String(m[1]).toLowerCase()); if (!target || target.state.meta.deletedAt) return notFound({ error: 'Not found', code: 'not-found' }); const host = String(target.state.meta.name).toLowerCase(); const hit = sessionOn(target); if (hit) return handOff(hit.engine, hit.id, '/', host); return { status: 302, headers: { Location: `${scheme}//${host}${hostSuffix}/`, 'Cache-Control': 'no-store' }, body: '' }; } if (route === 'POST /api/auth/logout') { const everywhere = ['1', 'true'].includes(rx.searchParams?.get('everywhere') ?? ''); const borrowed = !everywhere && !!session && session.engine !== weave; const ending = everywhere ? hub.entries().map(([, w]) => ownSession(w)).filter(Boolean) : borrowed ? [] : [session].filter(Boolean); for (const hit of ending) { try { hit.engine.revokeSession(hit.id, { id: hit.sessionId }); } catch {} } const names = new Set(everywhere ? [...Object.keys(cookies).filter((n) => n === LEGACY_COOKIE || n.startsWith(`${LEGACY_COOKIE}_`) || n.startsWith('wv_out_')), ...ending.map((hit) => hit.cookie)] : borrowed ? [] : [session?.cookie ?? cookieName(weave)]); const marks = borrowed ? [`${outName(weave)}=1; HttpOnly; SameSite=Lax; Path=/; Max-Age=${Weave.SESSION_TTL_MS / 1000}${secureFlag(rx)}`] : []; const held = everywhere ? await heldStart() : null; if (held) starts.delete(held.key); const startEnds = everywhere ? [clearCookie('wv_start', rx), clearRail()] : []; return out(200, { ok: true }, { 'Set-Cookie': [...[...names].map((n) => clearCookie(n, rx)), ...marks, ...startEnds], 'Cache-Control': 'no-store' }); } if (route === 'GET /api/auth/me') { const account = who(); if (!account) return deny(401, 'Not signed in'); const engine = session ? session.engine : weave; const sessions = engine.listSessions(account.id).map(({ accountId, ...s }) => ({ ...s, current: s.id === session?.sessionId })); const { credentials, ...pub } = engine.listAccounts().find((a) => a.id === account.id) ?? account; const ids = pub.identities ?? []; const user = posthog ? (ids.find((i) => i.issuer === oidc?.issuer) ?? ids[0])?.subject : null; return out(200, { account: pub, role: account.role, sessions, ...(user ? { analytics: { user } } : {}) }, { 'Cache-Control': 'no-store' }); } if ((m = path.match(/^\/api\/auth\/sessions\/([^/]+)$/)) && rx.method === 'DELETE') { const account = who(); if (!account) return deny(401, 'Not signed in'); const engine = session ? session.engine : weave; if (m[1] === 'others') return out(200, engine.revokeSession(account.id, { all: true, except: session?.sessionId ?? null })); return out(200, engine.revokeSession(account.id, { id: m[1] })); } return notFound({ error: 'Unknown auth route', code: 'not-found' }); } if (startDoor) { if (hostOrigin || wsPrefix || !oidc) return notFound({ error: 'Not found', code: 'not-found' }); const held = await heldStart(); const logins = held?.logins ?? []; if (route === 'POST /api/start/signout') { if (held) starts.delete(held.key); return out(200, { ok: true }, { 'Set-Cookie': [clearCookie('wv_start', rx), clearRail()], 'Cache-Control': 'no-store' }); } if (route === 'GET /api/start' && !logins.length) return out(200, { signedIn: false, provider: oidc.name, ...(clerkPane ? { clerk: clerkPane } : {}), ...(posthog ? { analytics: { key: posthog.key } } : {}) }, { 'Cache-Control': 'no-store' }); const navigation = ['GET', 'HEAD'].includes(rx.method) && (rx.header('sec-fetch-mode') === 'navigate' || prefersHtml(rx.header('accept'))); if (!logins.length && navigation) { const q = rx.searchParams?.toString() ?? ''; const link = `${path}${q ? `?${q}` : ''}`; return { status: 302, headers: { Location: START_LINK.test(link) ? `/start?next=${encodeURIComponent(link)}` : '/start', 'Cache-Control': 'no-store' }, body: '' }; } if (!logins.length) return deny(401, 'Sign in first'); const found = findWorkspaces(logins); const { accountIn } = found; if (route === 'GET /api/start') { const { canCreate, accountName } = found; const trialOn = async (name) => { const w = billing && hub.get(name); if (!w || !billing.trial(w.billing())) return null; return (await billing.standing({ workspace: w.billing(), accounts: w.listAccounts() })).trial; }; const rows = await Promise.all(found.rows.map(async (r) => (r.kind === 'workspace' ? { ...r, trial: await trialOn(r.name) } : r))); let waiting = null; if (billing && found.owner) { try { waiting = await found.unpaid(found.owner); } catch (error) { if (!(error instanceof BillingUnavailable)) throw error; } } return out(200, { signedIn: true, rows, logins: found.logins, canCreate, waiting, openSignup: openSignup.on, accountName, base: baseDomain, docs: hub.entries().filter(([, w]) => isDocsWorkspace(w)).map(([name]) => name), ...(clerkPane ? { clerk: clerkPane } : {}), ...(posthog ? { analytics: { key: posthog.key, user: logins[0].subject } } : {}) }, { 'Set-Cookie': railCookie(found.names), 'Cache-Control': 'no-store' }); } if (route === 'GET /api/start/slug') { if (limited('slugs', clientIp(rx))) return out(429, { error: 'Too many checks: wait a minute and try again', code: 'rate-limited' }); const slug = String(rx.searchParams?.get('slug') ?? ''); return out(200, { slug, state: hub.slugState(slug), base: baseDomain }, { 'Cache-Control': 'no-store' }); } if ((m = path.match(/^\/api\/start\/open\/([^/]+)$/)) && rx.method === 'GET') { const w = hub.bySlug(String(m[1]).toLowerCase()); const account = w && accountIn(w); if (!account && navigation) { const ws = w ? w.state.meta.name : String(m[1]); return out(403, renderRefusalPage({ title: `No access to ${ws}`, lines: [`The account you signed in with has no access to ${ws}.`, 'A workspace architect can send you an invite link that adds you. Or open one of your own workspaces.'], actions: [{ href: '/start?pick=1', label: 'Find your workspaces' }] }), { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } if (!account) return deny(403, 'This sign-in opens no account on that workspace'); const engine = w; const host = String(w.state.meta.name).toLowerCase(); const deep = hostPath(rx.searchParams?.get('path')); const table = String(rx.searchParams?.get('table') ?? ''); const land = rx.searchParams?.get('to') === 'billing' ? 'billing' : rx.searchParams?.get('to') === 'resume' ? '#/resume' : /^[0-9a-f-]{36}$/.test(table) && w.listTables().some((t) => t.id === table) ? `#/table/${table}` : ''; const rail = railCookie(isDocsWorkspace(w) ? found.names : [host, ...found.names.filter((name) => name !== host)]); if (baseDomain) return withCookies(await handOff(engine, account.id, deep ?? `/${land}`, host), [rail]); const minted = engine.createSession(account.id, { ua: rx.header('user-agent') }); const back = cookies[outName(w)] ? [clearCookie(outName(w), rx)] : []; return { status: 302, headers: { Location: `/w/${encodeURIComponent(w.state.meta.name)}/${land}`, 'Set-Cookie': [sessionCookie(cookieName(engine), minted.token, rx), ...back, rail], 'Cache-Control': 'no-store' }, body: '' }; } if ((m = path.match(/^\/api\/start\/logo\/([^/]+)$/)) && rx.method === 'GET') { const w = hub.bySlug(String(m[1]).toLowerCase()); if (!(w && accountIn(w))) return deny(403, 'This sign-in opens no account on that workspace'); if (!w.state.meta.logo) return notFound({ error: 'This workspace has no logo', code: 'not-found' }); const { bytes } = w.getWorkspaceLogo(); return out(200, bytes, { 'Content-Type': logoType(bytes), 'Cache-Control': 'private, max-age=86400', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "sandbox; default-src 'none'" }); } if ((m = path.match(/^\/api\/start\/invites\/([^/]+)\/([A-Za-z0-9_-]+)$/)) && rx.method === 'POST') { const w = hub.bySlug(String(m[1]).toLowerCase()); if (!w) return notFound({ error: 'Not found', code: 'not-found' }); const me = logins.find((l) => l.email && w.invitesForEmail(l.email).some((i) => i.id === m[2])) ?? logins[0]; const account = w.acceptInvite(m[2], me); await remirror(w); return out(200, { name: w.state.meta.name, account: account.name, open: `/api/start/open/${encodeURIComponent(w.state.meta.name)}` }); } if ((m = path.match(/^\/api\/start\/workspaces\/([^/]+)\/agent$/)) && rx.method === 'GET') { const w = hub.bySlug(String(m[1]).toLowerCase()); const account = w && accountIn(w); if (!account) return deny(403, 'This sign-in opens no account on that workspace'); const seen = w.agentSeen(account.id); return out(200, { connected: !!seen, at: seen?.at ?? null, client: seen?.client ?? null }, { 'Cache-Control': 'no-store' }); } if (route === 'POST /api/start/workspaces') { const admission = await creationAdmission(logins); if (admission.response) return admission.response; const me = admission.me; const template = body?.template ? WeaveStarters.TEMPLATES.find((t) => t.id === body.template) : null; if (body?.template && !template) return out(400, { error: `Unknown template '${body.template}'`, code: 'invalid' }); const ownerName = found.accountName || 'owner'; let asked = body?.name; let slug = body?.slug ?? null; if (body?.quick) { asked = String(asked || ownerName); const base = WeaveSlugs.slugify(asked) || 'workspace'; slug = [base, ...Array.from({ length: 50 }, (_, i) => `${base}-${i + 2}`)].find((s) => hub.slugState(s) === 'available') ?? null; } const w = hub.create(asked, { slug, actor: `creator: ${weave.actorName(actorForRequest)}` }); const owner = w.createAccount({ name: ownerName, role: 'architect' }).account; w.redeemIdentityInvite(w.linkIdentity(owner.name, { issuer: me.issuer }).code, me); w.state.meta.signup = { issuer: me.issuer, subject: me.subject }; w.save(); w.startTrial(); let table = null; let templateError = null; const built = { space: template?.space ?? null, tables: [] }; if (template) { try { const spec = WeaveStarters.spec(template); const done = unrestricted(() => w.build(spec)); if (!done.ok) throw new Error(done.errors.map((e) => e.error).join('; ')); built.tables = spec.spaces[0].tables.map((t) => t.name); table = w.findTable(`${template.space}/${WeaveStarters.firstTable(template)}`)?.id ?? null; } catch (err) { templateError = err.message; } } w.markOnboarded(owner.id); const name = w.state.meta.name; const host = String(name).toLowerCase(); const mcp = baseDomain ? `${scheme}//${host}${hostSuffix}/mcp` : `${originFor(rx)}/w/${encodeURIComponent(name)}/mcp`; return out(201, { name, title: w.state.meta.title ?? name, open: `/api/start/open/${encodeURIComponent(name)}`, table, mcp, built, templateError }); } return notFound({ error: 'Not found', code: 'not-found' }); } if (route === 'GET /api/workspaces/slug') { if (!role && !session && weave.state.meta.requireAuth) return deny(401, 'Sign in to check a workspace address'); if (limited('slugs', clientIp(rx))) return out(429, { error: 'Too many checks: wait a minute and try again', code: 'rate-limited' }); const slug = String(rx.searchParams?.get('slug') ?? ''); return out(200, { slug, state: hub.slugState(slug), base: baseDomain }, { 'Cache-Control': 'no-store' }); } if (route === 'GET /api/health/build') { if (!role) return deny(401, 'A workspace token is required'); const serving = build() ?? {}; const candidate = serving.servingSha ?? serving.sha; const sha = /^[a-f0-9]{40}$/.test(candidate ?? '') ? candidate : null; return out(200, { sha, version, ready: sha != null && serving.stale !== true }, { 'Cache-Control': 'no-store' }); } if (route === 'GET /api/health') { const auth = signIn(); if (!mayAdminister(weave, role)) return out(200, { ok: true, name: 'weave', ...(role ? { version } : {}), ...(auth ? { auth: { ok: auth.ok } } : {}) }); const nightly = backup(); return out(200, { ok: true, name: 'weave', version, workspace: weave.state.meta.name, startedAt: STARTED_AT, uptime: Math.round(uptime()), ...(build() ?? {}), ...weave.storageStats(), ...(nightly ? { backup: nightly } : {}), ...(auth ? { auth } : {}) }); } const canOpen = (w) => !w.state.meta.requireAuth || roleOn(w) != null; const useTemplateInto = ({ space, workspace, name } = {}) => { if (workspace == null || String(workspace).trim() === '') throw new WeaveError('Name the workspace to build in: {workspace}, as GET /api/workspaces lists them', 'invalid'); const target = hub.get(String(workspace)); if (!target || target.state.meta.deletedAt) throw new WeaveError(`Workspace '${workspace}' not found`, 'not-found'); const key = hub.list().find((x) => x.id === target.state.meta.id)?.name ?? target.state.meta.name; const targetRole = roleOn(target); if ((target.state.meta.requireAuth && !targetRole) || !mayAdminister(target, targetRole)) throw new WeaveError(`You cannot build in ${key}: using a template there needs an architect on it`, 'forbidden'); target.maybeRefresh?.(); const was = target.actor; target.actor = actorOn(target); try { const r = unrestricted(() => weave.useTemplate(space, target, { name: name == null || name === '' ? undefined : String(name) })); const url = key === hub.defaultName ? `/#/space/${r.space.id}` : `/w/${key}/#/space/${r.space.id}`; return { space: r.space, workspace: key, url, plan: r.plan, skipped: r.skipped }; } finally { target.actor = was; } }; const creationSource = () => oauth?.account ?? (authz && /^Bearer /i.test(authz) ? weave.verifyToken(authz.slice(7).trim()) : ownSession(weave)); let creation = null; if (route === 'POST /api/workspaces' && (hub.walled || baseDomain || openSignup.on || billing)) { const source = creationSource(); const logins = (source?.identities ?? []).map(identity => ({ ...identity, name: source.name })); creation = await creationAdmission(logins); if (creation.response) return creation.response; } const ownCreated = (w) => { const source = creationSource(); let owner = null; if (source) { const identities = source.identities ?? []; const existing = identities.map(identity => w.accountForIdentity(identity)).find(Boolean); if (existing) { w.setAccountRole(existing.id, 'architect'); owner = { account: w.listAccounts().find(account => account.id === existing.id) }; } else { let name = source.name; const taken = new Set(w.listAccounts().map(a => a.name)); for (let i = 2; taken.has(name); i++) name = `${source.name} ${i}`; owner = w.createAccount({ name, role: 'architect' }); for (const identity of identities) w.redeemIdentityInvite(w.linkIdentity(owner.account.id, { issuer: identity.issuer }).code, identity); } w.setRequireAuth(true); } if (creation?.me) { w.state.meta.signup = { issuer: creation.me.issuer, subject: creation.me.subject }; w.save(); w.startTrial(); } const headers = owner && ownSession(weave) ? { 'Set-Cookie': sessionCookie(cookieName(w), w.createSession(owner.account.id, { ua: rx.header('user-agent') }).token, rx) } : {}; return { owner, headers }; }; if (/^\/api\/workspaces(\/|$)/.test(path) && rx.method !== 'GET') { const ref = /^\/api\/workspaces\/([^/]+)/.exec(path)?.[1]; const target = ref ? hub.get(decodeURIComponent(ref)) : weave; if (!target) return notFound({ error: 'Workspace not found', code: 'not-found' }); const targetRole = roleOn(target); const local = !hub.walled && !target.state.meta.requireAuth && !target.listAccounts().length; if (!local && targetRole !== 'architect') return deny(targetRole ? 403 : 401, 'Managing a workspace needs its architect'); } if (route === 'POST /api/workspaces' && body?.from != null) { const source = String(body.from).match(/^backup:(.+)$/); if (!source) throw new WeaveError('from takes backup:, an archive GET /api/backups lists', 'invalid'); if (body.key == null || body.key === '') throw new WeaveError('key is required: the passphrase the archive was sealed with', 'invalid'); if (!cloudBackups) return out(400, { error: 'This deployment keeps no backups: it has no bucket configured.', code: 'invalid' }); const bearer = authz && /^Bearer /i.test(authz) ? authz.slice(7).trim() : ''; const caller = bearer ? weave.verifyToken(bearer) : ownSession(weave); if (!caller) return deny(401, 'Migrating a backup needs an account on this workspace'); const archive = await cloudBackups.get(caller.id, source[1]); const w = await hub.createFrom(body.name, { slug: body.slug ?? null, archive, key: body.key, workspace: body.workspace ?? null, actor: `creator: ${weave.actorName(actorForRequest)}` }); const { owner, headers } = ownCreated(w); return out(201, { name: w.state.meta.name, url: `/w/${w.state.meta.name}/`, from: `backup:${source[1]}`, entities: w.storageStats().entities, ...(owner ? { owner } : {}) }, headers); } if (route === 'POST /api/workspaces') { const w = hub.create(body.name, { slug: body.slug ?? null, actor: `creator: ${weave.actorName(actorForRequest)}` }); const { owner, headers } = ownCreated(w); return out(201, { name: w.state.meta.name, url: `/w/${w.state.meta.name}/`, ...(owner ? { owner } : {}) }, headers); } if ((m = path.match(/^\/api\/workspaces\/([^/]+)\/restore$/)) && rx.method === 'POST') { const w = hub.restore(m[1], { actor: actorOn(hub.get(m[1])) }); return out(200, { name: w.state.meta.name, deletedAt: null }); } if ((m = path.match(/^\/api\/workspaces\/([^/]+)$/)) && rx.method === 'DELETE') { if (!hub.remove) return out(400, { error: 'This deployment cannot delete workspaces' }); const hard = ['1', 'true'].includes(rx.searchParams.get('hard') ?? ''); if (hard) return out(200, hub.remove(m[1], { hard: true, actor: actorOn(hub.get(m[1])) })); const w = hub.remove(m[1], { actor: actorOn(hub.get(m[1])) }); return out(200, { name: w.state.meta.name, deletedAt: w.state.meta.deletedAt }); } if (path === '/api/onboarding') { const token = authz && /^Bearer /i.test(authz) ? weave.verifyToken(authz.slice(7).trim()) : null; const me = token ? { engine: weave, id: token.id, name: token.name } : session ? { engine: session.engine, id: session.id, name: session.name } : { engine: weave, id: null, name: null }; const taken = (n) => { const h = hub.get(n); return !!h && h !== weave; }; const firstName = WeaveStarters.workspaceName(String(me.name ?? '').trim().split(/\s+/)[0].toLowerCase()); const fallback = WeaveStarters.workspaceName(weave.state.meta.name) || 'workspace'; const byDefault = firstName && !taken(firstName) ? firstName : fallback; if (rx.method === 'GET') { const show = !me.engine.onboardedAt(me.id) && (!role || role === 'architect') && weave.state.meta.name !== 'weave' && !weave.userTables().length && hub.list().every((x) => x.name === 'weave' || !x.tables || hub.get(x.name) === weave || !canOpen(hub.get(x.name))); return out(200, { show, name: byDefault }, { 'Cache-Control': 'no-store' }); } if (rx.method === 'POST') { const name = WeaveStarters.workspaceName(body?.name) || byDefault; if (taken(name)) throw new WeaveError(slugTaken(name).message, 'slug_taken'); const template = body?.template ? WeaveStarters.TEMPLATES.find((t) => t.id === body.template) : null; if (body?.template && !template) throw new WeaveError(`Unknown template '${body.template}'`, 'invalid'); let table = null; if (template) { const built = weave.build(WeaveStarters.spec(template)); if (!built.ok) throw new WeaveError(`Couldn't set up ${template.title}: ${built.errors.map((e) => e.error).join('; ')}`, 'invalid'); table = weave.findTable(`${template.space}/${WeaveStarters.firstTable(template)}`)?.id ?? null; } const ws = updateWorkspace({ name }); me.engine.markOnboarded(me.id); return out(200, { id: ws.id, name: ws.name, url: `/w/${ws.id}/`, table }); } } if (route === 'GET /api/workspace/home') return out(200, weave.workspaceHome()); if (path === '/api/billing' || path === '/api/billing/organization') { if (!billing) return notFound({ error: 'This deployment bills nobody: WEAVE_CLERK_SECRET_KEY and WEAVE_BILLING_PLAN are not set.', code: 'billing-off' }); const architect = (unseated ?? role) === 'architect' || mayAdminister(weave, unseated ?? role); const standingNow = async () => { const accounts = weave.listAccounts(); const s = await billing.standing({ workspace: weave.billing(), accounts }); return { plan: s.plan, trial: s.trial ?? null, seats: s.seats, orgId: s.orgId, planSlug: billing.plan, publishableKey: billing.publishableKey, me: accountName, architect, members: accounts.map((a) => { const mail = a.identities?.find((i) => i.verifiedEmail)?.verifiedEmail ?? null; return { name: a.name, role: Weave.roleName(a.role), paid: PAID_ROLES.has(Weave.roleName(a.role)), seated: s.seated(a.name), ...(architect && mail ? { email: mail } : {}) }; }), }; }; const answer = async (extra = {}) => { try { return out(200, { ...(await standingNow()), ...extra }, { 'Cache-Control': 'no-store' }); } catch (error) { if (!(error instanceof BillingUnavailable)) throw error; return out(503, { error: error.message, code: 'billing-unavailable' }, { 'Retry-After': '60', 'Cache-Control': 'no-store' }); } }; if (route === 'GET /api/billing') { if (!role && !unseated && !mayAdminister(weave, role)) return deny(401, 'Sign in to see this workspace\'s plan'); return answer(); } if (route === 'POST /api/billing/organization') { if (!architect) return deny(role ? 403 : 401, 'Setting up billing needs an architect'); const me = weave.listAccounts().find((a) => a.name === accountName); const createdBy = me ? billing.subjectOf(me) : null; if (!createdBy) return out(400, { error: 'Your account has no sign-in identity at the billing provider, so it cannot own the billing organization. Sign in with the provider first.', code: 'invalid' }); weave.linkBillingOrg(await billing.ensureOrg({ workspace: weave.billing(), createdBy })); const synced = await remirror(weave); billing.forget(); return answer(synced?.refused?.length ? { refused: synced.refused } : {}); } return out(405, { error: 'Billing answers GET /api/billing and POST /api/billing/organization', code: 'method-not-allowed' }, { Allow: 'GET, POST' }); } if (path.startsWith('/api/accounts') || path.startsWith('/api/invites') || (route === 'PATCH /api/workspace' && ('requireAuth' in (body ?? {}) || 'linkPreview' in (body ?? {})))) { if (!mayAdminister(weave, role)) { return deny(role ? 403 : 401, 'Managing accounts needs an architect token'); } } if ((m = path.match(/^\/api\/mail\/preview\/([^/]+)$/)) && rx.method === 'GET') { if (!mayAdminister(weave, role)) return deny(role ? 403 : 401, 'Previewing email needs an architect token'); const make = { invite: inviteEmail, trial: trialEmail, 'trial-last': (o) => trialEmail({ ...o, stage: 'last' }) }[m[1]]; if (!make) return notFound({ error: `No email named '${m[1]}' (invite, trial, trial-last)`, code: 'not-found' }); const asked = rx.searchParams?.get('role') || 'editor'; if (!MAIL_ROLES[asked]) throw new WeaveError(`Invalid role '${asked}' (${Object.keys(MAIL_ROLES).join(', ')})`, 'invalid'); const o = originFor(rx); const email = make({ inviter: 'kyle', workspace: 'weave', expires: 'October 10', role: asked, daysLeft: 3, endsAt: '2026-10-10T00:00:00.000Z', billing: `${o}/w/weave/billing`, link: `${o}/api/auth/oidc/start?invite=Zq7tK4mW2xR9pLc8`, origin: o, theme: ['light', 'dark'].includes(rx.searchParams?.get('theme')) ? rx.searchParams.get('theme') : undefined }); return out(200, email.html, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); } if ((m = path.match(/^\/api\/integrations\/([^/]+)\/credential$/))) { if (!role || !mayAdminister(weave, roleOn(weave))) return deny(role ? 403 : 401, 'Setting an integration key needs an architect on this workspace'); if (rx.method !== 'PUT') return out(405, { error: 'An integration key is written with PUT and never read back', code: 'method-not-allowed' }, { Allow: 'PUT' }); return out(200, await weave.setIntegrationCredential(decodeURIComponent(m[1]), body?.value)); } if (path.startsWith('/api/keys')) { if (!role) return deny(401, 'Managing keys needs an account on this workspace'); const localRole = roleOn(weave); const km = /^\/api\/keys\/([^/]+)(\/reveal)?$/.exec(path); const reveal = !!km?.[2] && rx.method === 'POST'; const target = route === 'POST /api/keys' ? body?.name : km && !km[2] && rx.method === 'DELETE' ? decodeURIComponent(km[1]) : null; const owns = !!localRole && target != null && weave.ownsKey(target); if (!mayAdminister(weave, localRole) && !(reveal && localRole) && !owns) { return deny(localRole ? 403 : 401, 'Managing keys needs an architect on this workspace'); } const keyed = matchRoute(rx.method, path); if (keyed) { const r = httpReply(weave, keyed, keyed.cmd.http.body ? await rx.readBody() : body, rx.searchParams); return out(r.status, r.data, r.headers); } if (/^\/api\/keys\/.+$/.test(path)) return out(404, { error: 'Secrets cannot be read back', code: 'not-found' }); } if (path.startsWith('/api/shares') && !mayAdminister(weave, role)) return deny(role ? 403 : 401, 'Share links are minted, listed, renewed and revoked by an architect'); if (/^\/api\/views\/[^/]+\/share$/.test(path) && ['POST', 'DELETE'].includes(rx.method) && !mayAdminister(weave, role)) { return deny(role ? 403 : 401, 'Sharing a view, or ending its link, needs an architect'); } if (route === 'POST /api/uploads') { return out(201, prepareUpload({ entity: body.entity, field: body.field, name: body.name, mime: body.mime, size: body.size, preview: body.preview, sameOrigin: true })); } if (mcpDoor && rx.method !== 'POST') return out(405, { error: 'The MCP door takes POST: JSON-RPC in, JSON out, no stream', code: 'method-not-allowed' }, { Allow: 'POST' }); if (route === 'POST /api/mcp') { const msgs = Array.isArray(body) ? body : [body]; const seat = unseated ? `${seatRequired()} Billing: ${hostOrigin ?? mcpOrigin(rx)}${wsPrefix}/billing` : null; const caller = { role, tier: unseated ?? (role ? topRole(weave, viewer) : role), workspaceTier: unseated ?? role, workspaceOrder: () => hub.list({ workspace: weave }).filter(x => canOpen(hub.get(x.name))), moveWorkspace: (ref, position) => hub.move(ref, position, { workspace: weave, visible: canOpen }).filter(x => canOpen(hub.get(x.name))), updateWorkspace, useTemplate: useTemplateInto, seat, prepareUpload }; const replies = msgs.map((msg) => handleMcpMessage(weave, msg, { version, caller })).filter(Boolean); if (viewers.tokenWrite) { for (const msg of msgs) { const tool = msg?.method === 'tools/call' ? (msg.params?.name === 'weave_call' ? msg.params?.arguments?.name : msg.params?.name) : null; if (tool && !SEATLESS_TOOLS.has(tool)) weave.noteTokenWrite(viewers.tokenWrite.accountId, { method: 'MCP', path: tool, status: 200 }); } } if (oauth) oauth.engine.noteAgent(oauth.account.id, rx.header('user-agent')); if (!replies.length) return { status: 202, headers: { 'Content-Type': 'application/json' }, body: '' }; return out(200, Array.isArray(body) ? replies : replies[0], { 'Cache-Control': 'no-store' }); } if (route === 'POST /api/import' && !mayAdminister(weave, role)) return deny(role ? 403 : 401, 'Replacing the workspace needs an architect token'); if (route === 'POST /api/bulk' && role === 'editor') { const { ids, op, ...params } = body ?? {}; const sys = (fn) => { try { return !!fn()?.system; } catch { return false; } }; const list = ids == null ? [] : [].concat(ids); const into = op === 'rollup' && params.table == null ? (() => { try { return weave.relationTargetDbIds(weave.getField(weave.getEntity(list[0]).dbId, params.field)); } catch { return []; } })() : []; if (list.some((id) => sys(() => weave.state.tables[weave.getEntity(id).dbId])) || (params.table != null && sys(() => weave.getTable(params.table))) || into.some((t) => weave.state.tables[t]?.system)) { return deny(403, 'This token cannot change the schema'); } } if (route === 'POST /api/markdown') { return out(200, { html: renderMarkdown(String(body.md ?? ''), { resolveMention, rawHtml: false }) }); } if (route === 'GET /api/forms/manage' || route === 'POST /api/forms' || (rx.method === 'PATCH' && /^\/api\/forms\/[^/]+$/.test(path))) { if ((weave.state.meta.requireAuth && !role) || !mayAdminister(weave, role)) return deny(role ? 403 : 401, 'Managing forms needs an architect on this workspace'); if (route === 'GET /api/forms/manage') { const tableRef = rx.searchParams?.get('table'); const table = tableRef ? weave.getTable(tableRef) : null; const forms = listForms(weave).filter(form => !table || form.tableId === table.id).map(form => getFormDefinition(weave, form.id)); return out(200, { forms, anonymousAllowed: anonymousForms, requireAuth: !!weave.state.meta.requireAuth }, { 'Cache-Control': 'no-store' }); } if (route === 'POST /api/forms') { if (body?.kind !== undefined) throw new WeaveError('The form builder creates record forms', 'invalid'); const form = createForm(weave, body); return out(201, getFormDefinition(weave, form.id)); } return out(200, updateForm(weave, decodeURIComponent(path.split('/').pop()), body)); } if (route === 'GET /api/forms') { if (isDocsWorkspace(weave)) { try { ensureBugForm(weave); } catch {} } return out(200, listForms(weave)); } if ((m = path.match(/^\/api\/forms\/([^/]+)$/)) && rx.method === 'GET') { const { form, admitted } = formFor(m[1]); if (!admitted) return deny(401, 'Sign in to open this form'); return out(200, form); } const stamp = () => ({ version, startedAt: STARTED_AT, uptime: Math.round(uptime()), workspace: weave.state.meta.name }); if (route === 'POST /api/workspace/leave') { refuseOnDocs(weave, 'left'); const me = session && session.engine === weave && !session.visitor ? session : role ? weave.verifyToken(authz?.slice(7).trim() ?? '') : null; if (!me) return deny(401, 'Sign in to leave a workspace'); const left = leaveWorkspace(weave, me.id); await remirror(weave); return out(200, left); } if (route === 'POST /api/bug-report') { const reporter = role == null ? 'bug-report' : `reporter: ${weave.actorName(actorForRequest)}`; const docs = hub.get('weave') ?? hub.get('weaver'); const issues = docs && (() => { try { return docs.getTable('Development/Issue'); } catch { return null; } })(); if (issues && role == null && !canOpen(docs)) return deny(401, 'The weave docs workspace requires authentication'); const target = bugForwardTarget(); let forwardFailure = null; if (target) { const sent = await forwardBugReport(target, body && typeof body === 'object' && !Array.isArray(body) ? body : {}, origin || process.env.WEAVE_ORIGIN || String(rx.header('host') ?? 'unknown')); if (sent.ok) return out(201, { ...sent.data, forwardedTo: target.url }); forwardFailure = { url: target.url, reason: sent.reason }; } const failed = forwardFailure ? { forward: 'failed', forwardError: forwardFailure.reason } : {}; if (!issues) { return out(501, { error: 'No Development/Issue table to file into — this instance has no weave docs workspace', code: 'unsupported', ...failed }); } docs.maybeRefresh(); const form = ensureBugForm(docs); const instance = role != null && typeof body?.instance === 'string' && body.instance.trim() ? body.instance.trim().slice(0, 200) : null; const filed = submitForm(docs, form.id, body ?? {}, { actor: docs === weave && role != null ? actorForRequest : reporter, server: { ...stamp(), instance, forwardFailure } }); return out(201, { ...filed, ...failed }); } const mailInvite = async (made) => { if (!mail) return { ...made, mailed: false }; try { const m = inviteEmail({ inviter: made.invitedBy, workspace: made.workspace, role: made.role, expires: longDate(made.expiresAt), link: made.url, origin: originFor(rx) }); await mail({ to: made.email, subject: m.subject, html: m.html, text: m.text }); return { ...made, mailed: true }; } catch (err) { console.error(`weave: the invite email was not sent: ${err.message}`); return { ...made, mailed: false, mailError: err.message }; } }; const registered = matchRoute(rx.method, path); if (registered?.cmd.http.serve) { const serveFile = (id) => { const preview = rx.searchParams?.has('preview') ?? false; const view = rx.searchParams?.has('view') ?? false; const known = weave.readFileMeta(id); if (!view || !isHtmlFile(known)) { const etag = `"${id}${preview ? '-preview' : ''}"`; const cache = { ETag: etag, 'Cache-Control': 'private, max-age=31536000, immutable' }; if (String(rx.header('if-none-match') ?? '').split(',').some((t) => [etag, `W/${etag}`, '*'].includes(t.trim()))) return out(304, '', cache); const { meta, bytes } = weave.readFile(id, { preview }); return out(200, bytes, { ...fileHeaders(meta, bytes, { view }), ...cache }); } const { meta, bytes } = weave.readFile(id, { preview }); if (!isHtmlFile(meta)) return out(200, bytes, fileHeaders(meta, bytes, { view })); const page = viewHtml(bytes, frameLinkBase(bytes) + frameStorageScript(weave.readFileStorage(id).items) + FRAME_HEIGHT_SCRIPT); return out(200, page, { ...fileHeaders(meta, bytes, { view }), 'Cache-Control': 'no-store' }); }; const serveLogo = () => { const { bytes } = weave.getWorkspaceLogo(); const type = logoType(bytes); return out(200, bytes, { 'Content-Type': type ?? 'application/octet-stream', ...(type ? {} : { 'Content-Disposition': 'attachment; filename="logo"' }), 'Cache-Control': 'no-cache', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "sandbox; default-src 'none'", }); }; return registered.cmd.http.serve(registered.params, { serveFile, serveLogo }); } if (registered) { const registryCtx = { viewerZone, workspace: member.state.meta.id, cap, share, useTemplate: useTemplateInto, updateWorkspace, issuer: oidc?.issuer, remirror: () => remirror(weave), mailInvite, start: oidc ? `${startRoot}/start` : undefined, workspaces: { list: () => { const includeDeleted = ['1', 'true'].includes(rx.searchParams.get('deleted') ?? ''); const rail = oidc ? String(cookies.wv_rail ?? '').split(',').filter(Boolean) : []; const heldOnRail = (x) => rail.includes(x.name.toLowerCase()) && !hub.get(x.name)?.state.meta.deletedAt; const rank = (x) => { const i = recent.indexOf(x.name.toLowerCase()); return i < 0 ? Infinity : i; }; return hub.list({ includeDeleted, workspace: weave }).filter((x) => canOpen(hub.get(x.name)) || heldOnRail(x)) .sort((a, b) => rank(a) - rank(b) || 0) .map((x) => ({ ...(canOpen(hub.get(x.name)) ? { ...x, deletable: x.deletable && mayAdminister(hub.get(x.name), roleOn(hub.get(x.name))) } : { name: x.name, id: x.id, held: true, open: `${startRoot}/api/start/open/${encodeURIComponent(x.name)}` }), ...(baseDomain ? { host: `${scheme}//${x.name.toLowerCase()}${hostSuffix}/` } : {}), })); }, move: (ref, position) => { hub.move(ref, position, { workspace: weave, visible: canOpen }); return hub.list({ workspace: weave }).filter((x) => canOpen(hub.get(x.name))); }, }, shareLink: (grant) => ({ ...grant, url: `${baseDomain ? apexOrigin : ''}/w/${weave.state.meta.id}/s/${grant.token}` }), get author() { return (role ? !['architect', 'editor'].includes(role) : guarded()) ? actorForRequest : undefined; }, origin: `${originFor(rx)}${wsPrefix}`, get expectedRevision() { return ifMatchRevision(rx.header('if-match')); }, }; const r = httpReply(weave, registered, registered.cmd.http.body ? await rx.readBody() : body, rx.searchParams, registryCtx); if (registered.cmd.http.finish) r.data = await registered.cmd.http.finish(r.data, registryCtx); return out(r.status, r.data, r.headers); } if ((m = path.match(/^\/api\/spaces\/([^/]+)$/))) { if (rx.method === 'GET') return out(200, weave.getSpace(m[1])); } if ((m = path.match(/^\/api\/spaces\/([^/]+)\/home$/)) && rx.method === 'GET') return out(200, weave.spaceHome(m[1])); if ((m = path.match(/^\/api\/tables\/([^/]+)$/))) { if (rx.method === 'GET') { const db = weave.getTable(m[1]); const schema = weave.describeSchema().flatMap((s) => s.tables).find((d) => d.id === db.id); return out(200, schema ?? { id: db.id, name: db.name, spaceId: db.spaceId, deletedAt: db.deletedAt ?? null }); } } if ((m = path.match(/^\/api\/tables\/([^/]+)\/entities$/))) { if (rx.method === 'GET') { const limit = boundedLimit(rx.searchParams.get('limit'), QUERY_PAGE); const offset = nonNegativeInteger('offset', rx.searchParams.get('offset')); return out(200, weave.query(m[1], { limit, offset, viewerZone, workspace: member.state.meta.id })); } } if ((m = path.match(/^\/api\/tables\/([^/]+)\/prefill$/)) && rx.method === 'GET') { return out(200, prefillDraft(weave, m[1], rx.searchParams)); } if ((m = path.match(/^\/api\/entities\/([^/]+)\/references$/)) && rx.method === 'GET') { return out(200, weave.referencesTo(m[1])); } if ((m = path.match(/^\/api\/entities\/([^/]+)\/view$/)) && rx.method === 'GET') { const shape = rx.searchParams.get('shape') ?? 'chip'; const rawCfg = rx.searchParams.get('config'); let config = null; if (rawCfg) { try { config = JSON.parse(rawCfg); } catch { throw new WeaveError('config is a JSON object', 'invalid'); } } return out(200, weave.renderView(m[1], shape, { config })); } if ((m = path.match(/^\/api\/entities\/([^/]+)\/references-from$/)) && rx.method === 'GET') { return out(200, weave.referencesFrom(m[1])); } if ((m = path.match(/^\/api\/entities\/([^/]+)\/version$/)) && rx.method === 'POST') { const promote = ['1', 'true'].includes(rx.searchParams.get('promote') ?? '') || body.promote === true; const { newSlideVersion } = await deckModule(); const made = newSlideVersion(weave, m[1], { promote }); return out(201, weave.readEntity(made.id)); } if ((m = path.match(/^\/api\/entities\/([^/]+)\/conditional$/)) && rx.method === 'POST') return out(200, weave.conditionalWrite(m[1], body)); if ((m = path.match(/^\/api\/files\/([^/]+)\/storage$/))) { if (rx.method === 'GET') return out(200, weave.readFileStorage(m[1])); if (rx.method === 'PUT') return out(200, weave.writeFileStorage(m[1], body.items)); } return out(404, { error: `No route: ${route}` }); } if (serveStatic) { const hit = serveStatic(path, rx); if (hit) return path === '/' ? stampRecent(hit) : hit; } return notFound('Not found'); } catch (err) { const status = statusFor(err); const json = errorBody(err, 'internal'); if (json.code === 'too-large' && ATTACH_ROUTE.test(path)) json.limit = FILE_MAX_BYTES; return status === 404 ? notFound(json) : out(status, json, status === 429 ? { 'Retry-After': '60' } : {}); } }; return async function handle(rx) { const jar = []; const viewers = { of: null }; const res = await withViewers(viewers, () => respond(rx, jar, viewers)); const wrote = viewers.tokenWrite; const reads = rx.method === 'POST' && (/^\/(api\/)?mcp$/.test(rx.path) || /\/query$/.test(rx.path) || rx.path === '/api/markdown'); if (wrote && !reads && !['GET', 'HEAD', 'OPTIONS'].includes(rx.method) && res.status < 400) wrote.engine.noteTokenWrite(wrote.accountId, { method: rx.method, path: rx.path, status: res.status }); return withCookies(res, jar); }; }