Files
zeekayandHanzo Dev 470b1a16be fix(bridge): tenant bridges call their own origin /api (no cross-brand, no CORS)
Zoo/Pars bridge UIs fetched networks from bridge-api.lux.network cross-origin
— a cross-brand call that the backend rejects for the bridge.zoo.network origin
(500), so chains never loaded. Route /api on each tenant host to the shared
bridge-server (a longer Prefix beats "/"), and point BRIDGE_API_HOST at the
host's own origin. Same-origin GET carries no Origin header, so networks load;
zero cross-brand leak in the network tab. Mirrors bridge.lux.network.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 15:46:04 -07:00

148 lines
11 KiB
YAML

---
###############################################################################
# Tenant bridges — Zoo + Pars (lux-k8s / lux-bridge ns)
#
# ONE brand-neutral image (ghcr.io/luxfi/bridge, app/bridge Vite SPA on
# ghcr.io/hanzoai/spa) white-labeled PER HOST by a mounted /__ENV.js that the
# SPA's bridge.config.ts reads at boot (window.__ENV → brand name/logo/favicon/
# footer). Same pattern as bridge.lux.network; only the ConfigMap differs.
#
# Routing: k8s Ingress `bridge-ingress` sends bridge.zoo.network + bridge.zoo.ngo
# -> zoo-bridge:80 and bridge.pars.network -> pars-bridge:80, with /api -> the
# shared bridge-server:3000 on each host so the tenant UI calls its OWN origin
# (BRIDGE_API_HOST below) — same-origin, no cross-brand call, no CORS.
###############################################################################
apiVersion: v1
data:
__ENV.js: |
window.__ENV = {
BRIDGE_API_HOST: 'https://bridge.zoo.network',
BRIDGE_ENV: 'mainnet',
BRIDGE_IAM_ORG: 'zoo',
BRIDGE_BRAND_NAME: 'Zoo Bridge',
BRIDGE_LOGO_URL: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDwhLS0gWm9vIGJyYW5kIG1hcms6IHRocmVlLWNpcmNsZSBDTVlLIFZlbm4gZGlhZ3JhbSwgZnVsbCBjb2xvci4KICAgICAgIEZpbGxlZCB0byB0aGUgdmlld3BvcnQgc28gaXQgc2NhbGVzIGNsZWFubHkgaW50byAzMngzMiBuYXYgc2xvdHMKICAgICAgIGFuZCAyNTZ4MjU2IE9HIGNhcmRzIHdpdGhvdXQgdGlueSBkZWFkIG1hcmdpbnMuIC0tPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJsb2dvQ2xpcCI+CiAgICAgIDxjaXJjbGUgY3g9IjUxMiIgY3k9IjUxMSIgcj0iNTAwIi8+CiAgICA8L2NsaXBQYXRoPgogICAgPGNsaXBQYXRoIGlkPSJsb2dvWWVsbG93Ij4KICAgICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iMjUwIiByPSI0MzAiLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8Y2xpcFBhdGggaWQ9ImxvZ29NYWdlbnRhIj4KICAgICAgPGNpcmNsZSBjeD0iMjQwIiBjeT0iNjcwIiByPSI0MzAiLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8Y2xpcFBhdGggaWQ9ImxvZ29DeWFuIj4KICAgICAgPGNpcmNsZSBjeD0iNzg0IiBjeT0iNjcwIiByPSI0MzAiLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgogIDxnIGNsaXAtcGF0aD0idXJsKCNsb2dvQ2xpcCkiPgogICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iMjUwIiByPSI0MzAiIGZpbGw9IiNGQ0YwMDYiLz4KICAgIDxjaXJjbGUgY3g9IjI0MCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjRUEwMThFIi8+CiAgICA8Y2lyY2xlIGN4PSI3ODQiIGN5PSI2NzAiIHI9IjQzMCIgZmlsbD0iIzAxQUNGMSIvPgogICAgPGcgY2xpcC1wYXRoPSJ1cmwoI2xvZ29ZZWxsb3cpIj4KICAgICAgPGNpcmNsZSBjeD0iMjQwIiBjeT0iNjcwIiByPSI0MzAiIGZpbGw9IiNFRDFDMjQiLz4KICAgIDwvZz4KICAgIDxnIGNsaXAtcGF0aD0idXJsKCNsb2dvWWVsbG93KSI+CiAgICAgIDxjaXJjbGUgY3g9Ijc4NCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjMDBBNjUyIi8+CiAgICA8L2c+CiAgICA8ZyBjbGlwLXBhdGg9InVybCgjbG9nb01hZ2VudGEpIj4KICAgICAgPGNpcmNsZSBjeD0iNzg0IiBjeT0iNjcwIiByPSI0MzAiIGZpbGw9IiMyRTMxOTIiLz4KICAgIDwvZz4KICAgIDxnIGNsaXAtcGF0aD0idXJsKCNsb2dvWWVsbG93KSI+CiAgICAgIDxnIGNsaXAtcGF0aD0idXJsKCNsb2dvTWFnZW50YSkiPgogICAgICAgIDxjaXJjbGUgY3g9Ijc4NCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjMDAwMDAwIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=',
BRIDGE_FAVICON_URL: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDwhLS0gWm9vIGJyYW5kIG1hcms6IHRocmVlLWNpcmNsZSBDTVlLIFZlbm4gZGlhZ3JhbSwgZnVsbCBjb2xvci4KICAgICAgIFNhbWUgbW90aWYgYXMgbG9nby5zdmcgYnV0IHNjYWxlZCB0byBmaWxsIHRoZSBmYXZpY29uIHZpZXdwb3J0IHNvCiAgICAgICB0aGUgY29sb3JzIHJlbWFpbiByZWFkYWJsZSBhdCAxNngxNiAvIDMyeDMyLiAtLT4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iZmF2Q2xpcCI+CiAgICAgIDxjaXJjbGUgY3g9IjUxMiIgY3k9IjUxMSIgcj0iNTAwIi8+CiAgICA8L2NsaXBQYXRoPgogICAgPGNsaXBQYXRoIGlkPSJmYXZZZWxsb3ciPgogICAgICA8Y2lyY2xlIGN4PSI1MTIiIGN5PSIyNTAiIHI9IjQzMCIvPgogICAgPC9jbGlwUGF0aD4KICAgIDxjbGlwUGF0aCBpZD0iZmF2TWFnZW50YSI+CiAgICAgIDxjaXJjbGUgY3g9IjI0MCIgY3k9IjY3MCIgcj0iNDMwIi8+CiAgICA8L2NsaXBQYXRoPgogICAgPGNsaXBQYXRoIGlkPSJmYXZDeWFuIj4KICAgICAgPGNpcmNsZSBjeD0iNzg0IiBjeT0iNjcwIiByPSI0MzAiLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgogIDxnIGNsaXAtcGF0aD0idXJsKCNmYXZDbGlwKSI+CiAgICA8Y2lyY2xlIGN4PSI1MTIiIGN5PSIyNTAiIHI9IjQzMCIgZmlsbD0iI0ZDRjAwNiIvPgogICAgPGNpcmNsZSBjeD0iMjQwIiBjeT0iNjcwIiByPSI0MzAiIGZpbGw9IiNFQTAxOEUiLz4KICAgIDxjaXJjbGUgY3g9Ijc4NCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjMDFBQ0YxIi8+CiAgICA8ZyBjbGlwLXBhdGg9InVybCgjZmF2WWVsbG93KSI+CiAgICAgIDxjaXJjbGUgY3g9IjI0MCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjRUQxQzI0Ii8+CiAgICA8L2c+CiAgICA8ZyBjbGlwLXBhdGg9InVybCgjZmF2WWVsbG93KSI+CiAgICAgIDxjaXJjbGUgY3g9Ijc4NCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjMDBBNjUyIi8+CiAgICA8L2c+CiAgICA8ZyBjbGlwLXBhdGg9InVybCgjZmF2TWFnZW50YSkiPgogICAgICA8Y2lyY2xlIGN4PSI3ODQiIGN5PSI2NzAiIHI9IjQzMCIgZmlsbD0iIzJFMzE5MiIvPgogICAgPC9nPgogICAgPGcgY2xpcC1wYXRoPSJ1cmwoI2ZhdlllbGxvdykiPgogICAgICA8ZyBjbGlwLXBhdGg9InVybCgjZmF2TWFnZW50YSkiPgogICAgICAgIDxjaXJjbGUgY3g9Ijc4NCIgY3k9IjY3MCIgcj0iNDMwIiBmaWxsPSIjMDAwMDAwIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=',
BRIDGE_PRIMARY_COLOR: '#00d97e',
BRIDGE_SUPPORT_EMAIL: '[email protected]',
BRIDGE_DOCS_URL: 'https://docs.zoo.network',
}
kind: ConfigMap
metadata:
name: zoo-bridge-env
namespace: lux-bridge
---
apiVersion: v1
data:
__ENV.js: |
window.__ENV = {
BRIDGE_API_HOST: 'https://bridge.pars.network',
BRIDGE_ENV: 'mainnet',
BRIDGE_IAM_ORG: 'pars',
BRIDGE_BRAND_NAME: 'Pars Bridge',
BRIDGE_LOGO_URL: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogICAgICAgIDxkZWZzPgogICAgICAgICAgICA8Y2xpcFBhdGggaWQ9Im91dGVyQ2lyY2xlQ29sb3IiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTExIiByPSIyNzAiLz4KICAgICAgICAgICAgPC9jbGlwUGF0aD4KICAgICAgICAgICAgPGNsaXBQYXRoIGlkPSJncmVlbkNsaXAiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iNTEzIiBjeT0iMzY5IiByPSIyMzQiLz4KICAgICAgICAgICAgPC9jbGlwUGF0aD4KICAgICAgICAgICAgPGNsaXBQYXRoIGlkPSJyZWRDbGlwIj4KICAgICAgICAgICAgICAgIDxjaXJjbGUgY3g9IjM2NSIgY3k9IjU5NSIgcj0iMjM0Ii8+CiAgICAgICAgICAgIDwvY2xpcFBhdGg+CiAgICAgICAgICAgIDxjbGlwUGF0aCBpZD0iYmx1ZUNsaXAiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iNjQzIiBjeT0iNTk1IiByPSIyMzQiLz4KICAgICAgICAgICAgPC9jbGlwUGF0aD4KICAgICAgICA8L2RlZnM+CiAgICAgICAgPGcgY2xpcC1wYXRoPSJ1cmwoI291dGVyQ2lyY2xlQ29sb3IpIj4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNTEzIiBjeT0iMzY5IiByPSIyMzQiIGZpbGw9IiMwMEE2NTIiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iMzY1IiBjeT0iNTk1IiByPSIyMzQiIGZpbGw9IiNFRDFDMjQiLz4KICAgICAgICAgICAgPGNpcmNsZSBjeD0iNjQzIiBjeT0iNTk1IiByPSIyMzQiIGZpbGw9IiMyRTMxOTIiLz4KICAgICAgICAgICAgPGcgY2xpcC1wYXRoPSJ1cmwoI2dyZWVuQ2xpcCkiPgogICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iMzY1IiBjeT0iNTk1IiByPSIyMzQiIGZpbGw9IiNGQ0YwMDYiLz4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8ZyBjbGlwLXBhdGg9InVybCgjZ3JlZW5DbGlwKSI+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSI2NDMiIGN5PSI1OTUiIHI9IjIzNCIgZmlsbD0iIzAxQUNGMSIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIGNsaXAtcGF0aD0idXJsKCNyZWRDbGlwKSI+CiAgICAgICAgICAgICAgICA8Y2lyY2xlIGN4PSI2NDMiIGN5PSI1OTUiIHI9IjIzNCIgZmlsbD0iI0VBMDE4RSIvPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxnIGNsaXAtcGF0aD0idXJsKCNncmVlbkNsaXApIj4KICAgICAgICAgICAgICAgIDxnIGNsaXAtcGF0aD0idXJsKCNyZWRDbGlwKSI+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBjeD0iNjQzIiBjeT0iNTk1IiByPSIyMzQiIGZpbGw9IiNGRkZGRkYiLz4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvc3ZnPg==',
BRIDGE_FAVICON_URL: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogICAgPGRlZnM+CiAgICAgIDxjbGlwUGF0aCBpZD0ib3V0ZXJDaXJjbGVNb25vIj4KICAgICAgICA8Y2lyY2xlIGN4PSI1MDgiIGN5PSI1MTAiIHI9IjI4MyI+PC9jaXJjbGU+CiAgICAgIDwvY2xpcFBhdGg+CiAgICA8L2RlZnM+CiAgICA8ZyBjbGlwLXBhdGg9InVybCgjb3V0ZXJDaXJjbGVNb25vKSI+CiAgICAgIDxjaXJjbGUgY3g9IjUxMyIgY3k9IjM2OSIgcj0iMjM0IiBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjMzIj48L2NpcmNsZT4KICAgICAgPGNpcmNsZSBjeD0iMzY1IiBjeT0iNTk1IiByPSIyMzQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMzMiPjwvY2lyY2xlPgogICAgICA8Y2lyY2xlIGN4PSI2NDMiIGN5PSI1OTUiIHI9IjIzNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIzMyI+PC9jaXJjbGU+CiAgICAgIDxjaXJjbGUgY3g9IjUwOCIgY3k9IjUxMCIgcj0iMjY1IiBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjM2Ij48L2NpcmNsZT4KICAgIDwvZz4KICA8L3N2Zz4=',
BRIDGE_PRIMARY_COLOR: '#C9A227',
BRIDGE_SUPPORT_EMAIL: '[email protected]',
BRIDGE_DOCS_URL: 'https://docs.pars.network',
}
kind: ConfigMap
metadata:
name: pars-bridge-env
namespace: lux-bridge
---
# ── Zoo Bridge ──────────────────────────────────────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: zoo-bridge
namespace: lux-bridge
labels: { app: zoo-bridge, tenant: zoo }
spec:
replicas: 1
selector:
matchLabels: { app: zoo-bridge }
template:
metadata:
labels: { app: zoo-bridge, tenant: zoo }
spec:
containers:
- name: bridge
image: ghcr.io/luxfi/bridge:main
imagePullPolicy: Always
ports: [{ containerPort: 3000, name: http }]
volumeMounts:
- { name: env-js, mountPath: /public/__ENV.js, subPath: __ENV.js, readOnly: true }
readinessProbe:
httpGet: { path: /health, port: http }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /health, port: http }
initialDelaySeconds: 10
periodSeconds: 30
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "300m", memory: "256Mi" }
volumes:
- name: env-js
configMap: { name: zoo-bridge-env }
---
apiVersion: v1
kind: Service
metadata:
name: zoo-bridge
namespace: lux-bridge
labels: { app: zoo-bridge }
spec:
type: ClusterIP
selector: { app: zoo-bridge }
ports: [{ name: http, port: 80, targetPort: http }]
---
# ── Pars Bridge ─────────────────────────────────────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: pars-bridge
namespace: lux-bridge
labels: { app: pars-bridge, tenant: pars }
spec:
replicas: 1
selector:
matchLabels: { app: pars-bridge }
template:
metadata:
labels: { app: pars-bridge, tenant: pars }
spec:
containers:
- name: bridge
image: ghcr.io/luxfi/bridge:main
imagePullPolicy: Always
ports: [{ containerPort: 3000, name: http }]
volumeMounts:
- { name: env-js, mountPath: /public/__ENV.js, subPath: __ENV.js, readOnly: true }
readinessProbe:
httpGet: { path: /health, port: http }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /health, port: http }
initialDelaySeconds: 10
periodSeconds: 30
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "300m", memory: "256Mi" }
volumes:
- name: env-js
configMap: { name: pars-bridge-env }
---
apiVersion: v1
kind: Service
metadata:
name: pars-bridge
namespace: lux-bridge
labels: { app: pars-bridge }
spec:
type: ClusterIP
selector: { app: pars-bridge }
ports: [{ name: http, port: 80, targetPort: http }]