@pablo2410/core-server 0.6.1 → 0.8.0

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.
package/dist/auth.d.ts CHANGED
@@ -1,6 +1,12 @@
1
- import * as express from 'express';
2
1
  import { Request, CookieOptions } from 'express';
3
2
 
3
+ /**
4
+ * Types for the createSubdomainAuth factory.
5
+ *
6
+ * These types define the contract between the shared auth SDK and each
7
+ * subdomain's local database, role mapping, and portal enrichment logic.
8
+ */
9
+
4
10
  /** Payload stored inside the session JWT. */
5
11
  interface SessionPayload {
6
12
  openId: string;
@@ -24,42 +30,12 @@ interface PortalUser {
24
30
  companyId?: number;
25
31
  siteId?: number;
26
32
  }
27
- interface ExchangeTokenResponse {
28
- accessToken: string;
29
- tokenType: string;
30
- expiresIn: number;
31
- refreshToken?: string;
32
- scope: string;
33
- idToken: string;
34
- }
35
- interface GetUserInfoResponse {
36
- openId: string;
37
- projectId: string;
38
- name: string;
39
- email?: string | null;
40
- platform?: string | null;
41
- loginMethod?: string | null;
42
- }
43
- interface GetUserInfoWithJwtResponse {
44
- openId: string;
45
- projectId: string;
46
- name: string;
47
- email?: string | null;
48
- platform?: string | null;
49
- loginMethod?: string | null;
50
- }
51
33
  /** Environment variables required by the auth SDK. */
52
34
  interface AuthEnv {
53
- /** Manus app ID (VITE_APP_ID) */
54
- appId: string;
55
- /** JWT secret for signing local sessions (JWT_SECRET) */
56
- jwtSecret: string;
57
35
  /** Shared portal JWT secret for cross-subdomain SSO (PORTAL_JWT_SECRET) */
58
36
  portalJwtSecret: string;
59
37
  /** Cookie domain for cross-subdomain sharing, e.g. ".oplyticsdigital.net" */
60
38
  cookieDomain: string;
61
- /** Manus OAuth server URL */
62
- oAuthServerUrl: string;
63
39
  /** Owner's openId for auto-admin assignment */
64
40
  ownerOpenId: string;
65
41
  /** Portal API base URL for user enrichment */
@@ -99,7 +75,7 @@ interface UpsertUserPayload {
99
75
  * This decouples the auth SDK from any specific ORM or schema.
100
76
  */
101
77
  interface DbAdapter<TUser extends BaseUser = BaseUser> {
102
- /** Find a user by their Manus openId. Returns null/undefined if not found. */
78
+ /** Find a user by their openId. Returns null/undefined if not found. */
103
79
  getUserByOpenId(openId: string): Promise<TUser | null | undefined>;
104
80
  /** Create or update a user record. */
105
81
  upsertUser(payload: UpsertUserPayload): Promise<void>;
@@ -159,30 +135,17 @@ interface SubdomainAuthConfig<TUser extends BaseUser = BaseUser> {
159
135
  interface SubdomainAuth<TUser extends BaseUser = BaseUser> {
160
136
  /** Verify a session cookie value. Returns payload or null. */
161
137
  verifySession(cookieValue: string | undefined | null): Promise<SessionPayload | null>;
162
- /** Create a signed session JWT for a given openId. */
163
- createSessionToken(openId: string, options?: {
164
- expiresInMs?: number;
165
- name?: string;
166
- }): Promise<string>;
167
138
  /**
168
139
  * Full authentication flow: verify cookie → find/create user → enrich
169
140
  * from portal → backfill enterprise. Returns the local user record.
170
141
  */
171
142
  authenticateRequest(req: Request): Promise<TUser>;
172
- /** Exchange an OAuth authorization code for tokens. */
173
- exchangeCodeForToken(code: string, state: string): Promise<ExchangeTokenResponse>;
174
- /** Get user info from Manus OAuth using an access token. */
175
- getUserInfo(accessToken: string): Promise<GetUserInfoResponse>;
176
- /** Get user info from Manus OAuth using a JWT. */
177
- getUserInfoWithJwt(jwtToken: string): Promise<GetUserInfoWithJwtResponse>;
178
143
  /** Fetch a user from the portal API by openId. */
179
144
  getPortalUser(openId: string): Promise<PortalUser | null>;
180
145
  /** Get cookie options for setting session cookies. */
181
146
  getSessionCookieOptions(req: Request): Pick<CookieOptions, "domain" | "httpOnly" | "path" | "sameSite" | "secure">;
182
147
  /** Validate a returnTo URL to prevent open-redirect attacks. */
183
148
  isValidReturnUrl(url: string): boolean;
184
- /** Register the /api/oauth/callback route on an Express app. */
185
- registerOAuthRoutes(app: express.Express): void;
186
149
  }
187
150
 
188
151
  /**
@@ -194,12 +157,10 @@ interface SubdomainAuth<TUser extends BaseUser = BaseUser> {
194
157
  * - Portal API user enrichment with X-Service-Key header
195
158
  * - Auto-create user from JWT payload if not in local DB
196
159
  * - Enterprise backfill from portal API
197
- * - Manus OAuth code exchange and user info
198
- * - OAuth callback route registration
199
160
  *
200
161
  * Based on SQDCP's battle-tested sdk.ts + cookies.ts.
201
162
  */
202
163
 
203
164
  declare function createSubdomainAuth<TUser extends BaseUser = BaseUser>(config: SubdomainAuthConfig<TUser>): SubdomainAuth<TUser>;
204
165
 
205
- export { type AuthEnv, type BaseUser, type DbAdapter, type ExchangeTokenResponse, type GetUserInfoResponse, type GetUserInfoWithJwtResponse, type PortalUser, type RoleMapper, type SessionPayload, type SubdomainAuth, type SubdomainAuthConfig, type UpsertUserPayload, createSubdomainAuth };
166
+ export { type AuthEnv, type BaseUser, type DbAdapter, type PortalUser, type RoleMapper, type SessionPayload, type SubdomainAuth, type SubdomainAuthConfig, type UpsertUserPayload, createSubdomainAuth };
package/dist/auth.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createSubdomainAuth
3
- } from "./chunk-LH6QZJM4.js";
3
+ } from "./chunk-K7X7X7SA.js";
4
4
  export {
5
5
  createSubdomainAuth
6
6
  };
@@ -1,26 +1,7 @@
1
1
  // src/auth/createSubdomainAuth.ts
2
2
  var DEFAULT_COOKIE_NAME = "app_session_id";
3
3
  var ONE_YEAR_MS = 1e3 * 60 * 60 * 24 * 365;
4
- var AXIOS_TIMEOUT_MS = 3e4;
5
- var EXCHANGE_TOKEN_PATH = "/webdev.v1.WebDevAuthPublicService/ExchangeToken";
6
- var GET_USER_INFO_PATH = "/webdev.v1.WebDevAuthPublicService/GetUserInfo";
7
- var GET_USER_INFO_WITH_JWT_PATH = "/webdev.v1.WebDevAuthPublicService/GetUserInfoWithJwt";
8
4
  var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
9
- function deriveLoginMethod(platforms, fallback) {
10
- if (fallback && fallback.length > 0) return fallback;
11
- if (!Array.isArray(platforms) || platforms.length === 0) return null;
12
- const set = new Set(
13
- platforms.filter((p) => typeof p === "string")
14
- );
15
- if (set.has("REGISTERED_PLATFORM_EMAIL")) return "email";
16
- if (set.has("REGISTERED_PLATFORM_GOOGLE")) return "google";
17
- if (set.has("REGISTERED_PLATFORM_APPLE")) return "apple";
18
- if (set.has("REGISTERED_PLATFORM_MICROSOFT") || set.has("REGISTERED_PLATFORM_AZURE"))
19
- return "microsoft";
20
- if (set.has("REGISTERED_PLATFORM_GITHUB")) return "github";
21
- const first = Array.from(set)[0];
22
- return first ? first.toLowerCase() : null;
23
- }
24
5
  function createSubdomainAuth(config) {
25
6
  const {
26
7
  env,
@@ -59,22 +40,7 @@ function createSubdomainAuth(config) {
59
40
  }
60
41
  return _axios;
61
42
  }
62
- let oauthClient = null;
63
- async function getOAuthClient() {
64
- if (oauthClient) return oauthClient;
65
- const axios = await getAxios();
66
- oauthClient = axios.create({
67
- baseURL: env.oAuthServerUrl,
68
- timeout: AXIOS_TIMEOUT_MS
69
- });
70
- return oauthClient;
71
- }
72
43
  let portalClient = null;
73
- function getPortalClient() {
74
- if (portalClient) return portalClient;
75
- if (!env.portalApiUrl) return null;
76
- return null;
77
- }
78
44
  async function getPortalClientAsync() {
79
45
  if (portalClient) return portalClient;
80
46
  if (!env.portalApiUrl) return null;
@@ -94,10 +60,6 @@ function createSubdomainAuth(config) {
94
60
  if (!secret) throw new Error("PORTAL_JWT_SECRET is not configured");
95
61
  return new TextEncoder().encode(secret);
96
62
  }
97
- function getSessionSecret() {
98
- const secret = env.portalJwtSecret || env.jwtSecret;
99
- return new TextEncoder().encode(secret);
100
- }
101
63
  function getCookieDomain() {
102
64
  return env.cookieDomain || void 0;
103
65
  }
@@ -129,53 +91,20 @@ function createSubdomainAuth(config) {
129
91
  return false;
130
92
  }
131
93
  }
132
- async function signSession(payload, options = {}) {
133
- const jose = await getJose();
134
- const issuedAt = Date.now();
135
- const expiresInMs = options.expiresInMs ?? sessionExpiryMs;
136
- const expirationSeconds = Math.floor((issuedAt + expiresInMs) / 1e3);
137
- const secretKey = getSessionSecret();
138
- return new jose.SignJWT({
139
- openId: payload.openId,
140
- appId: payload.appId,
141
- name: payload.name
142
- }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setExpirationTime(expirationSeconds).sign(secretKey);
143
- }
144
- async function createSessionToken(openId, options = {}) {
145
- return signSession(
146
- { openId, appId: env.appId, name: options.name || "" },
147
- options
148
- );
149
- }
150
94
  async function verifySession(cookieValue) {
151
95
  if (!cookieValue) {
152
96
  console.warn(`${tag} Missing session cookie`);
153
97
  return null;
154
98
  }
155
99
  const jose = await getJose();
156
- if (env.portalJwtSecret) {
157
- try {
158
- const portalKey = getPortalSecret();
159
- const { payload } = await jose.jwtVerify(cookieValue, portalKey, {
160
- algorithms: ["HS256"]
161
- });
162
- const { openId, appId, name } = payload;
163
- if (!isNonEmptyString(openId) || !isNonEmptyString(appId) || !isNonEmptyString(name)) {
164
- console.warn(`${tag} Portal JWT payload missing required fields`);
165
- return null;
166
- }
167
- return { openId, appId, name };
168
- } catch {
169
- }
170
- }
171
100
  try {
172
- const secretKey = getSessionSecret();
173
- const { payload } = await jose.jwtVerify(cookieValue, secretKey, {
101
+ const portalKey = getPortalSecret();
102
+ const { payload } = await jose.jwtVerify(cookieValue, portalKey, {
174
103
  algorithms: ["HS256"]
175
104
  });
176
105
  const { openId, appId, name } = payload;
177
106
  if (!isNonEmptyString(openId) || !isNonEmptyString(appId) || !isNonEmptyString(name)) {
178
- console.warn(`${tag} Session payload missing required fields`);
107
+ console.warn(`${tag} Portal JWT payload missing required fields`);
179
108
  return null;
180
109
  }
181
110
  return { openId, appId, name };
@@ -251,44 +180,6 @@ function createSubdomainAuth(config) {
251
180
  portalUserId: portalUser.id
252
181
  };
253
182
  }
254
- async function exchangeCodeForToken(code, state) {
255
- const client = await getOAuthClient();
256
- const redirectUri = atob(state);
257
- const { data } = await client.post(
258
- EXCHANGE_TOKEN_PATH,
259
- {
260
- clientId: env.appId,
261
- grantType: "authorization_code",
262
- code,
263
- redirectUri
264
- }
265
- );
266
- return data;
267
- }
268
- async function getUserInfo(accessToken) {
269
- const client = await getOAuthClient();
270
- const { data } = await client.post(
271
- GET_USER_INFO_PATH,
272
- { accessToken }
273
- );
274
- const loginMethod = deriveLoginMethod(
275
- data?.platforms,
276
- data?.platform ?? data.platform ?? null
277
- );
278
- return { ...data, platform: loginMethod, loginMethod };
279
- }
280
- async function getUserInfoWithJwt(jwtToken) {
281
- const client = await getOAuthClient();
282
- const { data } = await client.post(
283
- GET_USER_INFO_WITH_JWT_PATH,
284
- { jwtToken, projectId: env.appId }
285
- );
286
- const loginMethod = deriveLoginMethod(
287
- data?.platforms,
288
- data?.platform ?? data.platform ?? null
289
- );
290
- return { ...data, platform: loginMethod, loginMethod };
291
- }
292
183
  async function authenticateRequest(req) {
293
184
  const cookie = await getCookie();
294
185
  const cookies = req.headers.cookie ? new Map(Object.entries(cookie.parse(req.headers.cookie))) : /* @__PURE__ */ new Map();
@@ -339,26 +230,10 @@ function createSubdomainAuth(config) {
339
230
  user = await db.getUserByOpenId(sessionUserId) ?? null;
340
231
  }
341
232
  } catch (error) {
342
- try {
343
- const userInfo = await getUserInfoWithJwt(sessionCookie ?? "");
344
- await db.upsertUser({
345
- openId: userInfo.openId,
346
- name: userInfo.name || null,
347
- email: userInfo.email ?? null,
348
- loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
349
- lastSignedIn: signedInAt
350
- });
351
- user = await db.getUserByOpenId(userInfo.openId) ?? null;
352
- } catch (oauthError) {
353
- console.error(
354
- `${tag} All user sync methods failed:`,
355
- error,
356
- oauthError
357
- );
358
- throw Object.assign(new Error("Failed to sync user info"), {
359
- statusCode: 403
360
- });
361
- }
233
+ console.error(`${tag} Failed to sync user info:`, error);
234
+ throw Object.assign(new Error("Failed to sync user info"), {
235
+ statusCode: 403
236
+ });
362
237
  }
363
238
  }
364
239
  if (!user) {
@@ -391,62 +266,16 @@ function createSubdomainAuth(config) {
391
266
  });
392
267
  return user;
393
268
  }
394
- function registerOAuthRoutes(app) {
395
- app.get(
396
- "/api/oauth/callback",
397
- async (req, res) => {
398
- const code = typeof req.query.code === "string" ? req.query.code : void 0;
399
- const state = typeof req.query.state === "string" ? req.query.state : void 0;
400
- if (!code || !state) {
401
- res.status(400).json({ error: "code and state are required" });
402
- return;
403
- }
404
- try {
405
- const tokenResponse = await exchangeCodeForToken(code, state);
406
- const userInfo = await getUserInfo(tokenResponse.accessToken);
407
- if (!userInfo.openId) {
408
- res.status(400).json({ error: "openId missing from user info" });
409
- return;
410
- }
411
- await db.upsertUser({
412
- openId: userInfo.openId,
413
- name: userInfo.name || null,
414
- email: userInfo.email ?? null,
415
- loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
416
- lastSignedIn: /* @__PURE__ */ new Date()
417
- });
418
- const sessionToken = await createSessionToken(userInfo.openId, {
419
- name: userInfo.name || "",
420
- expiresInMs: sessionExpiryMs
421
- });
422
- const cookieOptions = getSessionCookieOptions(req);
423
- res.cookie(cookieName, sessionToken, {
424
- ...cookieOptions,
425
- maxAge: sessionExpiryMs
426
- });
427
- res.redirect(302, "/");
428
- } catch (error) {
429
- console.error(`${tag} OAuth callback failed`, error);
430
- res.status(500).json({ error: "OAuth callback failed" });
431
- }
432
- }
433
- );
434
- }
435
269
  return {
436
270
  verifySession,
437
- createSessionToken,
438
271
  authenticateRequest,
439
- exchangeCodeForToken,
440
- getUserInfo,
441
- getUserInfoWithJwt,
442
272
  getPortalUser,
443
273
  getSessionCookieOptions,
444
- isValidReturnUrl,
445
- registerOAuthRoutes
274
+ isValidReturnUrl
446
275
  };
447
276
  }
448
277
 
449
278
  export {
450
279
  createSubdomainAuth
451
280
  };
452
- //# sourceMappingURL=chunk-LH6QZJM4.js.map
281
+ //# sourceMappingURL=chunk-K7X7X7SA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/createSubdomainAuth.ts"],"sourcesContent":["/**\n * createSubdomainAuth — shared SSO auth factory for Oplytics.digital subdomains.\n *\n * Encapsulates:\n * - JWT session verification (PORTAL_JWT_SECRET first, fallback to JWT_SECRET)\n * - Cookie domain handling via COOKIE_DOMAIN env var\n * - Portal API user enrichment with X-Service-Key header\n * - Auto-create user from JWT payload if not in local DB\n * - Enterprise backfill from portal API\n *\n * Based on SQDCP's battle-tested sdk.ts + cookies.ts.\n */\n\nimport type { Request, CookieOptions } from \"express\";\nimport type {\n AuthEnv,\n BaseUser,\n DbAdapter,\n PortalUser,\n RoleMapper,\n SessionPayload,\n SubdomainAuth,\n SubdomainAuthConfig,\n} from \"./types\";\n\n// ─── Constants ──────────────────────────────────────────────────────\n\nconst DEFAULT_COOKIE_NAME = \"app_session_id\";\nconst ONE_YEAR_MS = 1000 * 60 * 60 * 24 * 365;\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nconst isNonEmptyString = (value: unknown): value is string =>\n typeof value === \"string\" && value.length > 0;\n\n// ─── Factory ────────────────────────────────────────────────────────\n\nexport function createSubdomainAuth<TUser extends BaseUser = BaseUser>(\n config: SubdomainAuthConfig<TUser>\n): SubdomainAuth<TUser> {\n const {\n env,\n db,\n // Default to identity (no role translation). Subdomains should adopt the\n // Portal's canonical roles verbatim; a custom mapper is legacy.\n mapRole = (role: string) => role,\n userSource = \"db\",\n cookieName = DEFAULT_COOKIE_NAME,\n sessionExpiryMs = ONE_YEAR_MS,\n serviceName = \"Subdomain\",\n portalUserCacheTtlMs = 30_000,\n } = config;\n\n const tag = `[${serviceName} Auth]`;\n\n if (userSource === \"db\" && !db) {\n throw new Error(\n `${tag} userSource \"db\" requires a db adapter in createSubdomainAuth config`\n );\n }\n\n // Short-TTL cache of Portal users (portal mode), keyed by openId, so we don't\n // hit the Portal on every request.\n const portalUserCache = new Map<string, { user: PortalUser; at: number }>();\n\n // ── Lazy-loaded dependencies (jose, cookie, axios) ──────────────\n // We use dynamic imports so consumers don't need to install these\n // as peer deps if they only use other core-server features.\n\n let _jose: typeof import(\"jose\") | null = null;\n async function getJose() {\n if (!_jose) _jose = await import(\"jose\");\n return _jose;\n }\n\n let _cookie: typeof import(\"cookie\") | null = null;\n async function getCookie() {\n if (!_cookie) _cookie = await import(\"cookie\");\n return _cookie;\n }\n\n let _axios: typeof import(\"axios\").default | null = null;\n async function getAxios() {\n if (!_axios) {\n const mod = await import(\"axios\");\n _axios = mod.default;\n }\n return _axios;\n }\n\n // ── Portal API client ──────────────────────────────────────────\n\n let portalClient: import(\"axios\").AxiosInstance | null = null;\n\n async function getPortalClientAsync() {\n if (portalClient) return portalClient;\n if (!env.portalApiUrl) return null;\n const axios = await getAxios();\n portalClient = axios.create({\n baseURL: env.portalApiUrl,\n timeout: 10_000,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(env.portalApiKey ? { \"X-Service-Key\": env.portalApiKey } : {}),\n },\n });\n return portalClient;\n }\n\n // ── JWT Secret ─────────────────────────────────────────────────\n\n function getPortalSecret() {\n const secret = env.portalJwtSecret;\n if (!secret) throw new Error(\"PORTAL_JWT_SECRET is not configured\");\n return new TextEncoder().encode(secret);\n }\n\n // ── Cookie Helpers ─────────────────────────────────────────────\n\n function getCookieDomain(): string | undefined {\n return env.cookieDomain || undefined;\n }\n\n function isSecureRequest(req: Request): boolean {\n if (req.protocol === \"https\") return true;\n const forwardedProto = req.headers[\"x-forwarded-proto\"];\n if (!forwardedProto) return false;\n const protoList = Array.isArray(forwardedProto)\n ? forwardedProto\n : forwardedProto.split(\",\");\n return protoList.some((p) => p.trim().toLowerCase() === \"https\");\n }\n\n function getSessionCookieOptions(\n req: Request\n ): Pick<\n CookieOptions,\n \"domain\" | \"httpOnly\" | \"path\" | \"sameSite\" | \"secure\"\n > {\n const secure = isSecureRequest(req);\n const domain = getCookieDomain();\n return {\n domain,\n httpOnly: true,\n path: \"/\",\n sameSite: \"lax\",\n secure,\n };\n }\n\n function isValidReturnUrl(url: string): boolean {\n if (url.startsWith(\"/\") && !url.startsWith(\"//\")) return true;\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\") return false;\n return (\n parsed.hostname === \"oplyticsdigital.net\" ||\n parsed.hostname.endsWith(\".oplyticsdigital.net\")\n );\n } catch {\n return false;\n }\n }\n\n // ── Session Verification ───────────────────────────────────────\n\n /**\n * Verify session cookie against PORTAL_JWT_SECRET (cross-subdomain SSO).\n *\n * Portal is the only issuer of session cookies for subdomains using this\n * factory — the old Manus OAuth login path (which signed its own cookies\n * with a local JWT_SECRET) was removed. A cookie that fails portal\n * verification is invalid, full stop.\n */\n async function verifySession(\n cookieValue: string | undefined | null\n ): Promise<SessionPayload | null> {\n if (!cookieValue) {\n console.warn(`${tag} Missing session cookie`);\n return null;\n }\n\n const jose = await getJose();\n\n try {\n const portalKey = getPortalSecret();\n const { payload } = await jose.jwtVerify(cookieValue, portalKey, {\n algorithms: [\"HS256\"],\n });\n const { openId, appId, name } = payload as Record<string, unknown>;\n if (\n !isNonEmptyString(openId) ||\n !isNonEmptyString(appId) ||\n !isNonEmptyString(name)\n ) {\n console.warn(`${tag} Portal JWT payload missing required fields`);\n return null;\n }\n return { openId, appId, name };\n } catch (error) {\n console.warn(\n `${tag} Session verification failed:`,\n (error as Error).message\n );\n return null;\n }\n }\n\n // ── Portal API ─────────────────────────────────────────────────\n\n async function getPortalUser(openId: string): Promise<PortalUser | null> {\n try {\n const client = await getPortalClientAsync();\n if (!client) return null;\n // The Portal Service API is under /api/service (base URL is the origin,\n // e.g. https://portal.oplyticsdigital.net). Must include the /api/service\n // prefix — omitting it 404s and silently strands enterprise resolution.\n const { data } = await client.get<PortalUser>(\n `/api/service/users/by-openid/${encodeURIComponent(openId)}`\n );\n return data ?? null;\n } catch (err: unknown) {\n const status = (err as any)?.response?.status;\n if (status === 401) {\n console.error(\n `${tag} Portal API key revoked (401) — enrichment disabled`\n );\n } else if (status === 404) {\n // User not found in portal — normal for first-time users\n } else {\n console.warn(\n `${tag} Portal user lookup failed:`,\n (err as Error).message\n );\n }\n return null;\n }\n }\n\n /** Portal user lookup with a short-TTL cache (portal mode). */\n async function getPortalUserCached(\n openId: string\n ): Promise<PortalUser | null> {\n if (portalUserCacheTtlMs > 0) {\n const hit = portalUserCache.get(openId);\n if (hit && Date.now() - hit.at < portalUserCacheTtlMs) {\n return hit.user;\n }\n }\n const user = await getPortalUser(openId);\n if (user && portalUserCacheTtlMs > 0) {\n portalUserCache.set(openId, { user, at: Date.now() });\n }\n return user;\n }\n\n /**\n * Build ctx.user entirely from the Portal record — no local persistence.\n * The single source of truth. ctx.user.id is the PORTAL user id. Roles and\n * enterprise come from the Portal verbatim (no mapping).\n *\n * On a genuine Portal outage (the by-openid lookup returns null) we keep the\n * user authenticated using their verified session claims, with role/enterprise\n * unresolved, rather than throwing. Throwing here would make createContext (in\n * every subdomain) swallow the error into `user = null`, which the AuthGate\n * renders as the Sign In screen — i.e. a transient Portal blip would log the\n * user out. Their session cookie is already valid; keep them in, degraded,\n * until the Portal recovers. (Enterprise-scoped procedures will 403 during the\n * outage; client array-guards keep pages from crashing.)\n */\n async function resolvePortalUser(\n openId: string,\n session: SessionPayload\n ): Promise<TUser> {\n const portalUser = await getPortalUserCached(openId);\n\n if (!portalUser) {\n console.warn(\n `${tag} Portal user unavailable for ${openId} — keeping session (role/enterprise unresolved until Portal recovers)`\n );\n return {\n id: 0,\n openId,\n name: session.name ?? null,\n email: null,\n role: \"\",\n enterpriseId: null,\n portalUserId: null,\n } as unknown as TUser;\n }\n\n const enterpriseId =\n portalUser.enterpriseId ?? portalUser.companyId ?? null;\n return {\n id: portalUser.id,\n openId: portalUser.openId ?? openId,\n name: portalUser.name ?? session.name ?? null,\n email: portalUser.email ?? null,\n role: portalUser.role,\n enterpriseId,\n portalUserId: portalUser.id,\n } as unknown as TUser;\n }\n\n // ── Authenticate Request ───────────────────────────────────────\n\n async function authenticateRequest(req: Request): Promise<TUser> {\n const cookie = await getCookie();\n const cookies = req.headers.cookie\n ? new Map(Object.entries(cookie.parse(req.headers.cookie)))\n : new Map<string, string>();\n\n const sessionCookie = cookies.get(cookieName);\n const session = await verifySession(sessionCookie);\n\n if (!session) {\n throw Object.assign(new Error(\"Invalid session cookie\"), {\n statusCode: 403,\n });\n }\n\n const sessionUserId = session.openId;\n const signedInAt = new Date();\n\n // ── Portal mode: single source of truth, no local mirror ────\n if (userSource === \"portal\") {\n return await resolvePortalUser(sessionUserId, session);\n }\n\n let user = (await db!.getUserByOpenId(sessionUserId)) ?? null;\n\n // ── Auto-create user from verified JWT ──────────────────────\n if (!user) {\n try {\n // Try portal API first for richer user info\n const portalUser = await getPortalUser(sessionUserId);\n\n if (portalUser) {\n console.log(\n `${tag} Cross-subdomain SSO: creating user from portal (${portalUser.email || sessionUserId})`\n );\n // Portal returns `enterpriseId`; `companyId` is a legacy alias.\n const portalEnterpriseId =\n portalUser.enterpriseId ?? portalUser.companyId ?? null;\n await db!.upsertUser({\n openId: sessionUserId,\n name: portalUser.name || session.name || null,\n email: portalUser.email ?? null,\n loginMethod: \"portal-sso\",\n role: mapRole(portalUser.role),\n lastSignedIn: signedInAt,\n ...(portalEnterpriseId != null\n ? {\n enterpriseId: portalEnterpriseId,\n portalUserId: portalUser.id,\n }\n : {}),\n });\n user = (await db!.getUserByOpenId(sessionUserId)) ?? null;\n } else {\n // Portal unavailable — create from JWT payload\n console.log(\n `${tag} Cross-subdomain SSO: creating user from JWT payload (${sessionUserId})`\n );\n await db!.upsertUser({\n openId: sessionUserId,\n name: session.name || null,\n loginMethod: \"portal-sso\",\n lastSignedIn: signedInAt,\n });\n user = (await db!.getUserByOpenId(sessionUserId)) ?? null;\n }\n } catch (error) {\n console.error(`${tag} Failed to sync user info:`, error);\n throw Object.assign(new Error(\"Failed to sync user info\"), {\n statusCode: 403,\n });\n }\n }\n\n if (!user) {\n throw Object.assign(new Error(\"User not found\"), { statusCode: 403 });\n }\n\n // ── Enterprise backfill ─────────────────────────────────────\n if (user.enterpriseId === null) {\n try {\n const portalUser = await getPortalUser(user.openId);\n // Portal returns `enterpriseId`; `companyId` is a legacy alias.\n const portalEnterpriseId =\n portalUser?.enterpriseId ?? portalUser?.companyId ?? null;\n if (portalEnterpriseId != null) {\n console.log(\n `${tag} Backfilling enterpriseId=${portalEnterpriseId} for user ${user.id}`\n );\n await db!.updateUserFields(user.id, {\n enterpriseId: portalEnterpriseId,\n ...(portalUser?.id ? { portalUserId: portalUser.id } : {}),\n });\n user = (await db!.getUserByOpenId(user.openId)) ?? user;\n }\n } catch (backfillError) {\n console.warn(\n `${tag} Failed to backfill enterpriseId for user ${user.id}:`,\n backfillError\n );\n // Non-fatal — user can still proceed\n }\n }\n\n // Touch lastSignedIn\n await db!.upsertUser({\n openId: user.openId,\n lastSignedIn: signedInAt,\n });\n\n return user;\n }\n\n // ── Return public API ──────────────────────────────────────────\n\n return {\n verifySession,\n authenticateRequest,\n getPortalUser,\n getSessionCookieOptions,\n isValidReturnUrl,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { HierarchyService, HierarchyServiceConfig, createHierarchyService } from './hierarchy.js';
2
- export { AuthEnv, BaseUser, DbAdapter, ExchangeTokenResponse, GetUserInfoResponse, GetUserInfoWithJwtResponse, PortalUser, RoleMapper, SessionPayload, SubdomainAuth, SubdomainAuthConfig, UpsertUserPayload, createSubdomainAuth } from './auth.js';
2
+ export { AuthEnv, BaseUser, DbAdapter, PortalUser, RoleMapper, SessionPayload, SubdomainAuth, SubdomainAuthConfig, UpsertUserPayload, createSubdomainAuth } from './auth.js';
3
3
  import '@pablo2410/shared-ui/hierarchy/core';
4
4
  import 'express';
5
5
 
@@ -12,44 +12,13 @@ declare const SQDCP_PILLAR_LABELS: Record<SqdcpPillar, string>;
12
12
  * Minimal environment interface required by core-server utilities.
13
13
  * Each subdomain provides its own ENV object that satisfies this shape.
14
14
  *
15
- * `forgeApiUrl`/`forgeApiKey` back the Manus "Forge" gateway, used by the
16
- * data API, map, image generation, notification, and transcription helpers.
17
15
  * `geminiApiUrl`/`geminiApiKey` back the LLM chat client (llm.ts) directly
18
- * against Google's Gemini OpenAI-compatible endpoint — separate credential,
19
- * separate provider, not a Forge passthrough.
16
+ * against Google's Gemini OpenAI-compatible endpoint.
20
17
  */
21
18
  interface CoreEnv {
22
- forgeApiUrl: string;
23
- forgeApiKey: string;
24
19
  geminiApiUrl: string;
25
20
  geminiApiKey: string;
26
21
  }
27
- /**
28
- * Storage upload function signature.
29
- * Subdomains provide their own storagePut implementation.
30
- */
31
- type StoragePutFn = (key: string, data: Buffer | Uint8Array | string, contentType?: string) => Promise<{
32
- key: string;
33
- url: string;
34
- }>;
35
-
36
- /**
37
- * Data API helper — calls the Manus built-in Data API Hub.
38
- *
39
- * Quick example:
40
- * const callDataApi = createDataApiClient(ENV);
41
- * await callDataApi("Youtube/search", {
42
- * query: { gl: "US", hl: "en", q: "manus" },
43
- * });
44
- */
45
-
46
- type DataApiCallOptions = {
47
- query?: Record<string, unknown>;
48
- body?: Record<string, unknown>;
49
- pathParams?: Record<string, unknown>;
50
- formData?: Record<string, unknown>;
51
- };
52
- declare function createDataApiClient(env: CoreEnv): (apiId: string, options?: DataApiCallOptions) => Promise<unknown>;
53
22
 
54
23
  /**
55
24
  * Metering + cost-control primitives for the AI layer.
@@ -251,102 +220,6 @@ type InvokeResult = {
251
220
  };
252
221
  declare function createLLMClient(env: CoreEnv, options?: LLMClientOptions): (params: InvokeParams) => Promise<InvokeResult>;
253
222
 
254
- /**
255
- * Image generation helper using internal ImageService.
256
- *
257
- * Example:
258
- * import { storagePut } from "../storage";
259
- * const generateImage = createImageGenerator(ENV, storagePut);
260
- * const { url } = await generateImage({ prompt: "A serene landscape" });
261
- */
262
-
263
- type GenerateImageOptions = {
264
- prompt: string;
265
- originalImages?: Array<{
266
- url?: string;
267
- b64Json?: string;
268
- mimeType?: string;
269
- }>;
270
- };
271
- type GenerateImageResponse = {
272
- url?: string;
273
- };
274
- declare function createImageGenerator(env: CoreEnv, storagePut: StoragePutFn): (options: GenerateImageOptions) => Promise<GenerateImageResponse>;
275
-
276
- /**
277
- * Google Maps API Integration for Manus WebDev Templates.
278
- *
279
- * Main function: createMapClient(env) returns makeRequest<T>(endpoint, params)
280
- * All credentials are automatically injected. Array parameters use | as separator.
281
- *
282
- * Example:
283
- * const makeRequest = createMapClient(ENV);
284
- * const result = await makeRequest<GeocodeResult>(
285
- * "/maps/api/geocode/json",
286
- * { address: "1600 Amphitheatre Parkway" }
287
- * );
288
- */
289
-
290
- type MapRequestParams = Record<string, string | number | boolean | string[] | undefined>;
291
- declare function createMapClient(env: CoreEnv): <T = unknown>(endpoint: string, params?: MapRequestParams) => Promise<T>;
292
-
293
- /**
294
- * Owner notification helper — pushes operational updates to the Manus project owner.
295
- *
296
- * Example:
297
- * const notifyOwner = createNotificationClient(ENV);
298
- * await notifyOwner({ title: "New submission", content: "User X submitted form Y" });
299
- */
300
-
301
- type NotificationPayload = {
302
- title: string;
303
- content: string;
304
- };
305
- declare function createNotificationClient(env: CoreEnv): (payload: NotificationPayload) => Promise<boolean>;
306
-
307
- /**
308
- * Voice transcription helper using internal Speech-to-Text service (Whisper API).
309
- *
310
- * Example:
311
- * const transcribeAudio = createTranscriptionClient(ENV);
312
- * const result = await transcribeAudio({
313
- * audioUrl: "https://storage.example.com/audio/recording.mp3",
314
- * language: "en",
315
- * });
316
- */
317
-
318
- type TranscribeOptions = {
319
- audioUrl: string;
320
- language?: string;
321
- prompt?: string;
322
- };
323
- type WhisperSegment = {
324
- id: number;
325
- seek: number;
326
- start: number;
327
- end: number;
328
- text: string;
329
- tokens: number[];
330
- temperature: number;
331
- avg_logprob: number;
332
- compression_ratio: number;
333
- no_speech_prob: number;
334
- };
335
- type WhisperResponse = {
336
- task: string;
337
- language: string;
338
- duration: number;
339
- text: string;
340
- segments: WhisperSegment[];
341
- };
342
- type TranscriptionError = {
343
- error: string;
344
- code: string;
345
- details: string;
346
- };
347
- type TranscriptionResult = WhisperResponse | TranscriptionError;
348
- declare function createTranscriptionClient(env: CoreEnv): (options: TranscribeOptions) => Promise<TranscriptionResult>;
349
-
350
223
  /**
351
224
  * Personas for the shared Oplytics AI assistant.
352
225
  *
@@ -457,4 +330,4 @@ declare function createSupportEngine(env: CoreEnv, options?: SupportEngineOption
457
330
  };
458
331
  type SupportEngine = ReturnType<typeof createSupportEngine>;
459
332
 
460
- export { type AssistantMode, type BuildSystemPromptParams, type CallMeta, type ChatMessage, type ChatParams, type ChatResult, type CoreEnv, DEFAULT_COST_RATE, type DataApiCallOptions, type FileContent, type GenerateImageOptions, type GenerateImageResponse, type GuardRequest, type GuardResult, type ImageContent, type InvokeParams, type InvokeResult, KNOWLEDGE, type KnowledgeItem, LLMBudgetError, type LLMClientOptions, type MapRequestParams, type Message, type MessageContent, type NotificationPayload, type OutputSchema, type ResponseFormat, type RetrieveOptions, type Retriever, type Role, SQDCP_PILLARS, SQDCP_PILLAR_COLORS, SQDCP_PILLAR_LABELS, type SqdcpPillar, type StoragePutFn, type SupportContext, type SupportEngine, type SupportEngineOptions, type TextContent, type TokenCostRate, type Tool, type ToolChoice, type TranscribeOptions, type TranscriptionError, type TranscriptionResult, type UsageEvent, type WhisperResponse, type WhisperSegment, buildSystemPrompt, computeCostUsd, createDataApiClient, createImageGenerator, createLLMClient, createMapClient, createNotificationClient, createStaticRetriever, createSupportEngine, createTranscriptionClient, estimateMessageTokens, estimateTokens, formatKnowledge };
333
+ export { type AssistantMode, type BuildSystemPromptParams, type CallMeta, type ChatMessage, type ChatParams, type ChatResult, type CoreEnv, DEFAULT_COST_RATE, type FileContent, type GuardRequest, type GuardResult, type ImageContent, type InvokeParams, type InvokeResult, KNOWLEDGE, type KnowledgeItem, LLMBudgetError, type LLMClientOptions, type Message, type MessageContent, type OutputSchema, type ResponseFormat, type RetrieveOptions, type Retriever, type Role, SQDCP_PILLARS, SQDCP_PILLAR_COLORS, SQDCP_PILLAR_LABELS, type SqdcpPillar, type SupportContext, type SupportEngine, type SupportEngineOptions, type TextContent, type TokenCostRate, type Tool, type ToolChoice, type UsageEvent, buildSystemPrompt, computeCostUsd, createLLMClient, createStaticRetriever, createSupportEngine, estimateMessageTokens, estimateTokens, formatKnowledge };