mcpField
E
electric-proxy-auth

electric-proxy-auth

github.com/electric-sql开发10.4k54 次安装3 周前更新
代码仓库

关于 electric-proxy-auth

面向配置 Electric 认证代理的开发者技能,涉及租户范围的数据形状、安全转发参数、令牌刷新及代理响应处理。

主要功能

  1. 01配置租户范围的数据形状
  2. 02允许列表控制协议参数
  3. 03处理 401 令牌刷新
  4. 04配置 CORS 响应头
  5. 05安全管理密钥与 SQL 参数

适用场景

  1. 01构建服务端 Electric 代理
  2. 02按租户限制数据形状
  3. 03为数据流刷新令牌
  4. 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。

评论

支持 Markdown