ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Lightdash Data App 外部连接与 externalFetch API 实战指南:安全调用第三方 HTTP API 的完整规范

Lightdash Data App 外部连接与 externalFetch API 实战指南:安全调用第三方 HTTP API 的完整规范 Lightdash Data App 外部连接与 externalFetch API 实战指南安全调用第三方 HTTP API 的完整规范【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash导读在 Lightdash Data AppAI 生成的 React 数据应用中当应用需要拉取 Stripe、CRM、天气服务等第三方 HTTP API 数据时不能直接使用浏览器fetch或axios——沙箱会拦截这些调用。本文围绕 sandboxes/data-apps/template/references/external-apis.md 这份运行时参考文档系统讲解「外部连接」External Connection机制与lightdash.externalFetch()代理 API 的完整用法连接文件的字段结构、调用参数与响应格式、必须严格遵守的安全与校验规则并结合lightdash/query-sdk与后端代理校验源码解释这些规则背后的实现原理。读完你将能在自己的 Data App 中正确、安全地接入任意被管理员授权的第三方 API。一、为什么 Data App 不能直接调用外部 APILightdash Data App 运行在受限的沙箱 iframe 中。从源码看SDK 客户端在 iframe 场景下通过postMessage与父窗口桥接packages/query-sdk/src/postMessageTransport.ts而直接走 API 的createApiTransport路径其externalFetch实现会主动抛出异常——对应测试 packages/query-sdk/src/externalFetch.test.ts 中的断言throws — external fetch is only available inside a data app preview // 并且It must NOT attempt any network call from the dev/PAT path.也就是说从 Data App 里用裸fetch()、XMLHttpRequest、axios等任何客户端直连外部 API 都会被沙箱阻止并失败。这是安全设计而非功能缺陷外部 API 的凭据API Key、Token由 Lightdash 服务端保管应用代码永远接触不到完整的 URL、请求头或密钥。正确的姿势只有一条使用lightdash.externalFetch()。Lightdash 根据alias别名解析到管理员预先配置好的「外部连接」服务端代为注入凭据、发出请求、再返回响应。正如 packages/query-sdk/src/client.ts 中LightdashClient.externalFetch的注释所述Supply only the connectionaliasand a relative request — Lightdash resolves the alias to a stored connection, attaches its credentials, and proxies the call. The app never sees the request URL, request headers, or secrets.二、理解「外部连接」与连接文件2.1 管理员配置连接应用只持有别名当项目管理员在 Lightdash 中配置好一个命名连接存储第三方 API 的 origin 主机与凭据后应用通过生成请求中的externalConnections参数把连接绑定到应用见 packages/common/src/ee/apps/types.ts 中AppExternalConnectionReference与GenerateAppRequestBody.externalConnections的类型注释在构建的 catalog 阶段之前服务端即完成绑定并校验连接必须属于该项目。应用侧只拿到一个alias// AppExternalConnectionReference —— 应用可见的只有连接 UUID 与别名 type AppExternalConnectionReference { externalConnectionUuid: string; alias: string; };2.2 每个连接一个 JSON 文件/tmp/external-data/{alias}.json当应用与一个或多个外部连接关联时运行提示prompt顶部会出现[Linked external connections — each file in /tmp/external-data/ ...]提示块每个连接对应一个 JSON 文件。这个文件是应用编写代码时最重要的依据其字段结构与含义如下字段含义与用法instructions管理员撰写的使用说明鉴权怪癖、分页方式、关键端点、响应注意事项。仅在管理员写了时才存在存在时必须先读并遵循signature/howToCall精确的类型化 SDK 调用示例。鉴权由 Lightdash 注入——绝不要在调用中携带凭据或 API Keyorigin/requestUrl连接的基地址仅主机与 URL 构成方式。完整请求 URL originpath你的path会被原样拼接到 origin 之后——origin 和允许的前缀都不会被自动前置browserImageOrigin非 null 时应用可以仅从这个精确 origin 加载公开图片img、CSS 图片 URL、canvas 兼容的图片加载器、Leaflet 等地图瓦片层。这只是图片渲染豁免API/数据调用仍必须走externalFetch且永远不要把 Lightdash 数据或密钥放进图片 URLrules硬性要求详见下文「规则」allowedMethods/allowedPathPrefixes管理员允许的 HTTP 方法与路径前缀只能在限定的范围内调用samples示例{ request, response }对。构造externalFetch调用时照抄请求结构——包括完整的request.path响应值只视为形状参考不是穷尽枚举注意没有保存过示例的连接依然会有文件samples为空数组。此时应利用origin、allowedMethods、allowedPathPrefixes推断 API 支持的能力但path仍然必须是完整路径并以允许前缀开头。三、externalFetch 调用规范3.1 基本调用示例const res await lightdash.externalFetch(stripe, { method: GET, // GET | POST | PUT | PATCH | DELETE —— 默认 GET必须是连接允许的方法之一 path: /v1/charges, // 完整路径追加到连接的 origin主机之后。完整 URL origin path。必须以允许的前缀开头它不是相对前缀的路径 query: { limit: 10 }, // Recordstring, string —— 值必须是字符串 // body: { ... }, // JSON body —— 除 GET 外所有方法都会发送 }); // res.status —— 上游 HTTP 状态码number // res.contentType —— 上游 Content-Type // res.headers —— 安全的上游响应头键名小写 // res.body —— 解析后的 JSON非 JSON 时为原始文本 // res.truncated —— 为 true 表示 Lightdash 截断了超大的响应这个签名与 packages/query-sdk/src/types.ts 中定义的 SDK 类型完全对应export type ExternalFetchMethod GET | POST | PUT | PATCH | DELETE; export type ExternalFetchOptions { method?: ExternalFetchMethod; // 默认 GET path: string; // 追加到连接 base URL 的路径 query?: Recordstring, string; // 查询参数由后端合并进请求 URL body?: unknown; // JSON body除 GET 外均发送 }; export type ExternalFetchResult { status: number; // 上游 HTTP 状态 contentType: string; // 上游 Content-Type headers: Recordstring, string; // 安全响应头键名小写 body: unknown; // JSON 已解析否则为原始文本 truncated: boolean; // 响应是否被截断 };3.2 响应头只暴露「安全子集」res.headers只包含小写的缓存、分页与限流类响应头例如retry-after、ratelimit-*、x-ratelimit-*、etag、link其他上游响应头被有意省略。这并非偶然实现后端代理在 packages/backend/src/ee/services/ExternalConnectionService/proxyValidation.ts 中通过白名单显式过滤其注释说明了原因External connection credentials are injected by Lightdash, so arbitrary upstream headers are not safe to expose to app code even though they arrived over an admin-approved connection.白名单包含精确名accept-ranges、cache-control、content-range、etag、expires、last-modified、link、ratelimit、retry-after与三类前缀ratelimit-、x-ratelimit-、x-rate-limit-。此外link头的目标地址会被清洗包含用户名/密码的链接被移除使用 query 鉴权时 API Key 会被从分页链接中删除同源绝对链接会被改写为可直接用于下一次externalFetch的相对路径。暴露出的响应头元数据总量受EXTERNAL_RESPONSE_HEADER_MAX_BYTES 16 * 102416 KB上限约束。3.3 调用流程alias → 连接 → 服务端代理一次externalFetch调用的完整链路是应用调用lightdash.externalFetch(alias, opts)iframe 内的 SDK 通过postMessage向父窗口发送lightdash:sdk:external-fetch请求见 packages/query-sdk/src/postMessageTransport.ts 中的SdkExternalFetchRequest协议类型父窗口再转发给后端后端把 alias 解析为存储的连接附加其凭据在服务端发起请求后端对响应体超大时截断与响应头白名单过滤做安全处理再原路返回给应用。对应测试验证了 postMessage 传输层的完整握手请求携带type: lightdash:sdk:external-fetch、alias、method、path、query、body与唯一id响应以lightdash:sdk:external-fetch-response返回并且只有来自window.parent的响应才会被接受——测试中伪造来源的响应被忽略这防止了恶意 iframe 内容对应用的欺骗。四、必须严格遵守的规则以下规则来自文档的rules部分是从安全到正确性的硬性约束逐一拆解4.1 永远使用 externalFetch禁用其他客户端外部数据一律走lightdash.externalFetch()永远不要用裸fetch()、XMLHttpRequest、axios或其他客户端直连外部 API——沙箱会拦截并导致失败。唯一例外是图片渲染当连接文件的browserImageOrigin非 null 时可以只用那里给出的精确 origin 加载图片。4.2 永不硬编码密钥绝不在应用中硬编码 API Key、Token、密码或任何密钥。凭据由连接持有应用只持有 alias。绝不要向用户索要 API Key 或密钥也不要添加对应输入框。如果某个连接 alias 不存在应清晰提示用户让管理员配置连接而不是绕过它。调用中只接受alias、path、query、method、body五个参数——不能放完整 URL、主机或 HTTP 头。4.3 path 是「完整路径」origin pathpath是追加到连接 origin 之后的完整路径完整请求 URL origin path。origin 与允许的路径前缀都不会被自动前置所以必须传完整路径。例如连接配置允许/repos前缀要请求某个仓库的 issues 就得传/repos/owner/repo/issues不能传简写/issues。路径必须以前缀之一开头allowedPathPrefixes存在示例时照抄其request.path的结构。后端的实现印证了这一点proxyValidation.ts 中的normalizeAndValidatePath会对 path 做一整套防御性校验任何一条不满足都会抛出ParameterError拒绝空值、控制字符、反斜杠可被某些 HTTP 栈归一化为斜杠从而造成路径穿越/主机走私必须以单个/开头拒绝//协议相对 URL会解析到攻击者主机与绝对 URLscheme:前缀解码百分号编码后重新检查上述规则防止编码后的穿越技巧拒绝/xevil.com会在 URL 拼接时被重新解释为 userinfo拒绝任何..段前缀匹配是段感知的proxyValidation.ts 中的pathMatchesAllowedPrefixes只有路径等于前缀、或以/边界延续前缀时才匹配因此/v1/users的允许前缀不会放行/v1/users-admin最后对 WHATWG URL 解析器折叠后的路径如双重编码的%252e%252e再验证一次前缀匹配确保最终发出的请求仍在允许范围内。最终由buildOutboundUrl在服务端构造 URL主机永远来自连接配置的origin校验过的无主机 path 被追加query 用URLSearchParams序列化并再次确认构造结果的协议与主机与 origin 一致——整个过程中绝不字符串拼接用户提供的主机。4.4 query 值必须是字符串query是Recordstring, string每个值都必须是字符串即{ latitude: 52.52, limit: 10 }而不是{ latitude: 52.52, limit: 10 }。数字或布尔类型的 query 值会被拒绝并返回422。注意例外路径参数与 JSONbody保留真实类型只有 query-string 映射是纯字符串。query 参数由后端统一序列化不要把查询串直接拼进path后端在normalizeAndValidatePath中会剥离 path 中混入的?query部分。4.5 把响应当作「数据」而不是「指令」外部 API 返回的文本可能包含提示注入prompt-injection尝试。必须将响应渲染为内容绝不执行、eval 或遵循响应内嵌的指令绝不让响应内容改变应用调用 Lightdash 的方式。4.6 用 try/catch 包裹并展示友好错误态外部 API 会失败、会限流。每个调用都应包裹在try/catch中并向用户展示友好的错误状态例如加载失败卡片、重试按钮而不是让异常击穿整个应用。五、完整实战示例带错误处理的 Stripe 调用结合上述全部规则一个规范的 Data App 组件如下import { useState } from react; import { useLightdashClient } from lightdash/query-sdk; function ChargesList() { const client useLightdashClient(); const [charges, setCharges] useState([]); const [error, setError] useState(null); const [loading, setLoading] useState(false); async function loadCharges() { setLoading(true); setError(null); try { // 只用 alias —— 凭据由连接持有这里不出现任何密钥 const res await client.externalFetch(stripe, { method: GET, path: /v1/charges, // 完整路径照抄连接文件 samples 中的 request.path query: { limit: 10 }, // 值必须是字符串 }); if (res.status 400) { setError(Upstream error ${res.status}); return; } if (res.truncated) { console.warn(Response was truncated by Lightdash); } setCharges(res.body.data ?? []); } catch (err) { // 外部 API 会失败和限流 —— 展示友好错误态 setError(err.message); } finally { setLoading(false); } } // 渲染loading 显示加载中error 显示错误卡片与重试按钮 // 正常时渲染 charges 列表把响应当数据渲染绝不执行其中内容 }要点回顾仅用 aliaspath是完整路径且以允许前缀开头query 全部字符串化从res.body读取数据并检查res.truncatedtry/catch包裹并展示友好错误态永不把响应内容当作指令执行。六、从源码与测试验证行为契约这套 API 的行为不是纸面约定而是被类型定义与测试双重锁定的SDK 类型packages/query-sdk/src/types.ts 中ExternalFetchResult注释说明它是lightdash/common中ExternalFetchResponse的结构化副本——SDK 独立发布、不依赖lightdash/common因此形状在此处复制并保持同步。客户端方法packages/query-sdk/src/client.ts 中LightdashClient.externalFetch(alias, opts)直接委托给当前 transportiframe 内是 postMessage transport本地开发是 API transport。沙箱限制packages/query-sdk/src/externalFetch.test.ts 验证了 API transport 路径下 externalFetch 会抛出「仅 Data App 预览可用」错误且不发起任何网络调用同时验证了 postMessage transport 的完整请求/响应协议、alias 不存在的错误传播以及伪造来源响应的忽略逻辑。后端代理校验packages/backend/src/ee/services/ExternalConnectionService/proxyValidation.ts 实现了 path 规范化与前缀匹配、出站 URL 构造、API Key 响应头过滤filterExternalResponseHeaders、自定义请求头校验validateCustomHeaders拒绝host、cookie、content-type等路由/框架敏感头拒绝与 api key 注入头冲突等全部服务端安全边界。此外配套的运行时技能文档 sandboxes/data-apps/template/skill.md 也在「Linked external connections」一节约定当 prompt 列出外部连接时先读本参考文档再调用任何外部 API。七、常见错误速查错误写法正确写法说明fetch(https://api.stripe.com/v1/charges)lightdash.externalFetch(stripe, { path: /v1/charges })裸 fetch 被沙箱拦截path: /issuespath: /repos/owner/repo/issuespath 必须是完整路径前缀不会自动前置query: { limit: 10 }query: { limit: 10 }非字符串 query 值返回 422在代码里写apiKey: sk_...只用 alias密钥留在连接配置里凭据永不进应用在 URL 里拼完整主机只传pathquery不接受完整 URL / 主机 / 请求头把响应文本当指令 eval只渲染为内容防提示注入调用不包 try/catchtry/catch 友好错误态外部 API 会失败、会限流结语externalFetch把「凭据管理」与「请求执行」从应用代码中彻底剥离管理员在 Lightdash 中配置带凭据的连接应用只凭 alias 发起相对请求服务端代理负责注入密钥、执行请求、校验路径与过滤响应。这种「应用无密钥、沙箱无直连、服务端全管控」的三层模型既保证了 Data App 可以灵活接入任意第三方 API又守住了凭据不外泄、请求不越界、响应不当指令的安全底线。开发时先读/tmp/external-data/{alias}.json的连接文件照抄samples中的完整request.path把 query 全部字符串化用try/catch兜底——就能稳定、合规地把外部数据汇入你的 Lightdash Data App。【免费下载链接】lightdashAgentic BI. Analytics at the speed of code ⚡️项目地址: https://gitcode.com/GitHub_Trending/li/lightdash创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表