关于 electric-proxy-auth
面向配置 Electric 认证代理的开发者技能,涉及租户范围的数据形状、安全转发参数、令牌刷新及代理响应处理。
主要功能
- 01配置租户范围的数据形状
- 02允许列表控制协议参数
- 03处理 401 令牌刷新
- 04配置 CORS 响应头
- 05安全管理密钥与 SQL 参数
适用场景
- 01构建服务端 Electric 代理
- 02按租户限制数据形状
- 03为数据流刷新令牌
- 04防止不安全地转发参数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ electric-sql/ electric --skill electric-proxy-auth安装后在 Claude Code 中输入/electric-proxy-auth确认已加载。
详细介绍
本技能基于 electric-shapes。请先阅读该技能,了解 ShapeStream 配置。
Electric — 代理与身份验证
设置
import { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'
// Server route (Next.js App Router example)
export async function GET(request: Request) {
const url = new URL(request.url)
const originUrl = new URL('/v1/shape', process.env.ELECTRIC_URL)
// Only forward Electric protocol params — never table/where from client
url.searchParams.forEach((value, key) => {
if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
originUrl.searchParams.set(key, value)
}
})
// Server decides shape definition
originUrl.searchParams.set('table', 'todos')
originUrl.searchParams.set('secret', process.env.ELECTRIC_SOURCE_SECRET!)
const response = await fetch(originUrl)
const headers = new Headers(response.headers)
headers.delete('content-encoding')
headers.delete('content-length')
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
})
}
客户端用法:
import { ShapeStream } from '@electric-sql/client'
const stream = new ShapeStream({
url: '/api/todos', // Points to your proxy, not Electric directly
})
核心模式
使用 WHERE 参数隔离租户
// In proxy route — inject user context server-side
const user = await getAuthUser(request)
originUrl.searchParams.set('table', 'todos')
originUrl.searchParams.set('where', 'org_id = $1')
originUrl.searchParams.set('params[1]', user.orgId)
收到 401 时刷新身份验证令牌
const stream = new ShapeStream({
url: '/api/todos',
headers: {
Authorization: async () => `Bearer ${await getToken()}`,
},
onError: async (error) => {
if (error instanceof FetchError && error.status === 401) {
const newToken = await refreshToken()
return { headers: { Authorization: `Bearer ${newToken}` } }
}
return {}
},
})
为跨源代理配置 CORS
// In proxy response headers
headers.set(
'Access-Control-Expose-Headers',
'electric-offset, electric-handle, electric-schema, electric-cursor'
)
子集安全(AND 语义)
Electric 使用 AND 将主形状 WHERE(在代理中设置)与子集 WHERE(来自 POST 请求体)组合起来。子集只能缩小结果范围,不能扩大范围:
-- Main shape: WHERE org_id = $1 (set by proxy)
-- Subset: WHERE status = 'active' (from client POST)
-- Effective: WHERE org_id = $1 AND status = 'active'
即使子集中的 WHERE 1=1 也无法绕过主形状的 WHERE。
常见错误
严重:将所有客户端参数转发给 Electric
错误:
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value)
})
正确:
import { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'
url.searchParams.forEach((value, key) => {
if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(key)) {
originUrl.searchParams.set(key, value)
}
})
originUrl.searchParams.set('table', 'todos')
转发所有参数会让客户端控制 table、where 和 columns,从而访问任意 Postgres 表。只转发 ELECTRIC_PROTOCOL_QUERY_PARAMS。
来源:examples/proxy-auth/app/shape-proxy/route.ts
严重:未删除 content-encoding 和 content-length 标头
错误:
return new Response(response.body, {
status: response.status,
headers: response.headers,
})
正确:
const headers = new Headers(response.headers)
headers.delete('content-encoding')
headers.delete('content-length')
return new Response(response.body, { status: response.status, headers })
fetch() 会解压响应正文,但保留原始的 content-encoding 和 content-length 标头,导致浏览器解码失败。
来源:examples/proxy-auth/app/shape-proxy/route.ts:49-56
严重:向浏览器暴露 ELECTRIC_SECRET 或 SOURCE_SECRET
错误:
// Client-side code
const url = `/v1/shape?table=todos&secret=${import.meta.env.VITE_ELECTRIC_SOURCE_SECRET}`
正确:
// Server proxy only
originUrl.searchParams.set('secret', process.env.ELECTRIC_SOURCE_SECRET!)
Vite 等打包工具会将 VITE_* 环境变量暴露给客户端代码。密钥只能在服务端注入代理。
来源:AGENTS.md:17-20
严重:通过字符串插值在 WHERE 子句中造成 SQL 注入
错误:
originUrl.searchParams.set('where', `org_id = '${user.orgId}'`)
正确:
originUrl.searchParams.set('where', 'org_id = $1')
originUrl.searchParams.set('params[1]', user.orgId)
在 WHERE 子句中使用字符串插值会导致 SQL 注入。请使用位置参数($1、$2)。
来源:website/docs/guides/auth.md
高:Bun 的 fetch 并发上限(256)会在高负载下成为代理瓶颈
错误:
bun run proxy.ts
正确:
BUN_CONFIG_MAX_HTTP_REQUESTS=4096 bun run proxy.ts
Bun 默认将每个进程的同时 fetch() 调用数限制为 256。超出的请求会静默排队——代理仍会接受入站连接,但发往 Electric 的上游调用会停滞,表现为延迟激增而非错误。请提高 BUN_CONFIG_MAX_HTTP_REQUESTS(最大值为 65,336),以匹配预期的并发形状请求数。Node 和 Deno 没有此限制。
来源:https://bun.com/docs/runtime/networking/fetch
高:未通过 CORS 暴露 Electric 响应标头
错误:
// No CORS header configuration — browser strips custom headers
return new Response(response.body, { headers })
正确:
headers.set(
'Access-Control-Expose-Headers',
'electric-offset, electric-handle, electric-schema, electric-cursor'
)
return new Response(response.body, { headers })
如果 CORS 移除了 Electric 响应标头,客户端会抛出 MissingHeadersError。请暴露 electric-offset、electric-handle、electric-schema 和 electric-cursor。
来源:packages/typescript-client/src/error.ts:109-118
严重:在生产环境中从客户端直接调用 Electric
错误:
new ShapeStream({
url: 'https://my-electric.example.com/v1/shape',
params: { table: 'todos' },
})
正确:
new ShapeStream({
url: '/api/todos', // Your proxy route
})
Electric 的 HTTP API 默认公开且无需身份验证。始终通过服务器代理,以便服务器控制形状定义并注入密钥。
来源:AGENTS.md:19-20
另请参阅:electric-shapes/SKILL.md — 形状 URL 必须指向代理路由,而不是直接指向 Electric。 另请参阅:electric-deployment/SKILL.md — 生产环境需要 ELECTRIC_SECRET 和代理;开发环境使用 ELECTRIC_INSECURE=true。 另请参阅:electric-postgres-security/SKILL.md — 代理注入由 Postgres 安全机制强制执行的密钥。
版本
目标版本为 @electric-sql/client v1.5.10。