@wopr-network/platform-core 1.72.2 → 1.72.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,6 +73,7 @@ export function buildUpstreamHeaders(incoming, user, tenantSubdomain) {
73
73
  if (host)
74
74
  headers.set("host", host);
75
75
  headers.set("x-platform-user-id", user.id);
76
+ headers.set("x-paperclip-user-id", user.id); // compat: Paperclip sidecar reads this header
76
77
  headers.set("x-platform-tenant", tenantSubdomain);
77
78
  if (user.email)
78
79
  headers.set("x-platform-user-email", user.email);
@@ -81,17 +82,26 @@ export function buildUpstreamHeaders(incoming, user, tenantSubdomain) {
81
82
  return headers;
82
83
  }
83
84
  /**
84
- * Resolve the upstream container URL for a tenant subdomain from the
85
- * proxy route table. Returns null if no route exists or is unhealthy.
85
+ * Resolve the upstream container URL for a tenant subdomain.
86
+ *
87
+ * First checks the in-memory proxy route table (populated during
88
+ * provisioning). Falls back to deriving from the persistent profile
89
+ * data so that routing survives server restarts without needing
90
+ * Caddy or in-memory state.
86
91
  */
87
- function resolveContainerUrl(container, subdomain) {
92
+ function resolveContainerUrl(container, subdomain, profile) {
88
93
  if (!container.fleet)
89
94
  return null;
95
+ // Fast path: in-memory route table (populated during provisioning)
90
96
  const routes = container.fleet.proxy.getRoutes();
91
97
  const route = routes.find((r) => r.subdomain === subdomain);
92
- if (!route || !route.healthy)
93
- return null;
94
- return `http://${route.upstreamHost}:${route.upstreamPort}`;
98
+ if (route?.healthy) {
99
+ return `http://${route.upstreamHost}:${route.upstreamPort}`;
100
+ }
101
+ // Fallback: derive from persistent profile data (survives restarts)
102
+ const containerName = `wopr-${profile.name.replace(/_/g, "-")}`;
103
+ const port = profile.env?.PORT || "3100";
104
+ return `http://${containerName}:${port}`;
95
105
  }
96
106
  /**
97
107
  * Create a tenant subdomain proxy middleware.
@@ -133,10 +143,10 @@ export function createTenantProxyMiddleware(container, config) {
133
143
  if (!hasAccess) {
134
144
  return c.json({ error: "Forbidden: not a member of this tenant" }, 403);
135
145
  }
136
- // Resolve fleet container URL via proxy route table
137
- const upstream = resolveContainerUrl(container, subdomain);
146
+ // Resolve fleet container URL (route table or profile fallback)
147
+ const upstream = resolveContainerUrl(container, subdomain, profile);
138
148
  if (!upstream) {
139
- return c.json({ error: "Tenant not found" }, 404);
149
+ return c.json({ error: "Container unavailable" }, 503);
140
150
  }
141
151
  const url = new URL(c.req.url);
142
152
  const targetUrl = `${upstream}${url.pathname}${url.search}`;
@@ -68,7 +68,23 @@ export function mountRoutes(app, container, config, plugins = []) {
68
68
  if (container.fleet) {
69
69
  app.use("*", createTenantProxyMiddleware(container, {
70
70
  platformDomain: config.platformDomain,
71
- resolveUser: async () => undefined, // Products override via plugin or direct middleware
71
+ resolveUser: async (req) => {
72
+ try {
73
+ const { getAuth } = await import("../auth/better-auth.js");
74
+ const auth = getAuth();
75
+ const session = await auth.api.getSession({ headers: req.headers });
76
+ if (!session?.user)
77
+ return undefined;
78
+ return {
79
+ id: session.user.id,
80
+ email: session.user.email ?? undefined,
81
+ name: session.user.name ?? undefined,
82
+ };
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ },
72
88
  }));
73
89
  }
74
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.72.2",
3
+ "version": "1.72.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -95,6 +95,7 @@ export function buildUpstreamHeaders(incoming: Headers, user: ProxyUserInfo, ten
95
95
  const host = incoming.get("host");
96
96
  if (host) headers.set("host", host);
97
97
  headers.set("x-platform-user-id", user.id);
98
+ headers.set("x-paperclip-user-id", user.id); // compat: Paperclip sidecar reads this header
98
99
  headers.set("x-platform-tenant", tenantSubdomain);
99
100
  if (user.email) headers.set("x-platform-user-email", user.email);
100
101
  if (user.name) headers.set("x-platform-user-name", user.name);
@@ -102,15 +103,31 @@ export function buildUpstreamHeaders(incoming: Headers, user: ProxyUserInfo, ten
102
103
  }
103
104
 
104
105
  /**
105
- * Resolve the upstream container URL for a tenant subdomain from the
106
- * proxy route table. Returns null if no route exists or is unhealthy.
106
+ * Resolve the upstream container URL for a tenant subdomain.
107
+ *
108
+ * First checks the in-memory proxy route table (populated during
109
+ * provisioning). Falls back to deriving from the persistent profile
110
+ * data so that routing survives server restarts without needing
111
+ * Caddy or in-memory state.
107
112
  */
108
- function resolveContainerUrl(container: PlatformContainer, subdomain: string): string | null {
113
+ function resolveContainerUrl(
114
+ container: PlatformContainer,
115
+ subdomain: string,
116
+ profile: { name: string; env?: Record<string, string> },
117
+ ): string | null {
109
118
  if (!container.fleet) return null;
119
+
120
+ // Fast path: in-memory route table (populated during provisioning)
110
121
  const routes = container.fleet.proxy.getRoutes();
111
122
  const route = routes.find((r) => r.subdomain === subdomain);
112
- if (!route || !route.healthy) return null;
113
- return `http://${route.upstreamHost}:${route.upstreamPort}`;
123
+ if (route?.healthy) {
124
+ return `http://${route.upstreamHost}:${route.upstreamPort}`;
125
+ }
126
+
127
+ // Fallback: derive from persistent profile data (survives restarts)
128
+ const containerName = `wopr-${profile.name.replace(/_/g, "-")}`;
129
+ const port = profile.env?.PORT || "3100";
130
+ return `http://${containerName}:${port}`;
114
131
  }
115
132
 
116
133
  /**
@@ -161,10 +178,10 @@ export function createTenantProxyMiddleware(
161
178
  return c.json({ error: "Forbidden: not a member of this tenant" }, 403);
162
179
  }
163
180
 
164
- // Resolve fleet container URL via proxy route table
165
- const upstream = resolveContainerUrl(container, subdomain);
181
+ // Resolve fleet container URL (route table or profile fallback)
182
+ const upstream = resolveContainerUrl(container, subdomain, profile);
166
183
  if (!upstream) {
167
- return c.json({ error: "Tenant not found" }, 404);
184
+ return c.json({ error: "Container unavailable" }, 503);
168
185
  }
169
186
 
170
187
  const url = new URL(c.req.url);
@@ -106,7 +106,21 @@ export function mountRoutes(
106
106
  "*",
107
107
  createTenantProxyMiddleware(container, {
108
108
  platformDomain: config.platformDomain,
109
- resolveUser: async () => undefined, // Products override via plugin or direct middleware
109
+ resolveUser: async (req: Request) => {
110
+ try {
111
+ const { getAuth } = await import("../auth/better-auth.js");
112
+ const auth = getAuth();
113
+ const session = await auth.api.getSession({ headers: req.headers });
114
+ if (!session?.user) return undefined;
115
+ return {
116
+ id: session.user.id,
117
+ email: session.user.email ?? undefined,
118
+ name: session.user.name ?? undefined,
119
+ };
120
+ } catch {
121
+ return undefined;
122
+ }
123
+ },
110
124
  }),
111
125
  );
112
126
  }