@pablo2410/core-server 0.6.0 → 0.7.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 +9 -41
- package/dist/auth.js +1 -1
- package/dist/{chunk-LH6QZJM4.js → chunk-B63ROBSD.js} +7 -135
- package/dist/chunk-B63ROBSD.js.map +1 -0
- package/dist/index.d.ts +9 -133
- package/dist/index.js +4 -333
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-LH6QZJM4.js.map +0 -1
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,14 @@ 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
35
|
/** JWT secret for signing local sessions (JWT_SECRET) */
|
|
56
36
|
jwtSecret: string;
|
|
57
37
|
/** Shared portal JWT secret for cross-subdomain SSO (PORTAL_JWT_SECRET) */
|
|
58
38
|
portalJwtSecret: string;
|
|
59
39
|
/** Cookie domain for cross-subdomain sharing, e.g. ".oplyticsdigital.net" */
|
|
60
40
|
cookieDomain: string;
|
|
61
|
-
/** Manus OAuth server URL */
|
|
62
|
-
oAuthServerUrl: string;
|
|
63
41
|
/** Owner's openId for auto-admin assignment */
|
|
64
42
|
ownerOpenId: string;
|
|
65
43
|
/** Portal API base URL for user enrichment */
|
|
@@ -99,7 +77,7 @@ interface UpsertUserPayload {
|
|
|
99
77
|
* This decouples the auth SDK from any specific ORM or schema.
|
|
100
78
|
*/
|
|
101
79
|
interface DbAdapter<TUser extends BaseUser = BaseUser> {
|
|
102
|
-
/** Find a user by their
|
|
80
|
+
/** Find a user by their openId. Returns null/undefined if not found. */
|
|
103
81
|
getUserByOpenId(openId: string): Promise<TUser | null | undefined>;
|
|
104
82
|
/** Create or update a user record. */
|
|
105
83
|
upsertUser(payload: UpsertUserPayload): Promise<void>;
|
|
@@ -169,20 +147,12 @@ interface SubdomainAuth<TUser extends BaseUser = BaseUser> {
|
|
|
169
147
|
* from portal → backfill enterprise. Returns the local user record.
|
|
170
148
|
*/
|
|
171
149
|
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
150
|
/** Fetch a user from the portal API by openId. */
|
|
179
151
|
getPortalUser(openId: string): Promise<PortalUser | null>;
|
|
180
152
|
/** Get cookie options for setting session cookies. */
|
|
181
153
|
getSessionCookieOptions(req: Request): Pick<CookieOptions, "domain" | "httpOnly" | "path" | "sameSite" | "secure">;
|
|
182
154
|
/** Validate a returnTo URL to prevent open-redirect attacks. */
|
|
183
155
|
isValidReturnUrl(url: string): boolean;
|
|
184
|
-
/** Register the /api/oauth/callback route on an Express app. */
|
|
185
|
-
registerOAuthRoutes(app: express.Express): void;
|
|
186
156
|
}
|
|
187
157
|
|
|
188
158
|
/**
|
|
@@ -194,12 +164,10 @@ interface SubdomainAuth<TUser extends BaseUser = BaseUser> {
|
|
|
194
164
|
* - Portal API user enrichment with X-Service-Key header
|
|
195
165
|
* - Auto-create user from JWT payload if not in local DB
|
|
196
166
|
* - Enterprise backfill from portal API
|
|
197
|
-
* - Manus OAuth code exchange and user info
|
|
198
|
-
* - OAuth callback route registration
|
|
199
167
|
*
|
|
200
168
|
* Based on SQDCP's battle-tested sdk.ts + cookies.ts.
|
|
201
169
|
*/
|
|
202
170
|
|
|
203
171
|
declare function createSubdomainAuth<TUser extends BaseUser = BaseUser>(config: SubdomainAuthConfig<TUser>): SubdomainAuth<TUser>;
|
|
204
172
|
|
|
205
|
-
export { type AuthEnv, type BaseUser, type DbAdapter, type
|
|
173
|
+
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,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,16 +40,6 @@ 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
44
|
function getPortalClient() {
|
|
74
45
|
if (portalClient) return portalClient;
|
|
@@ -143,7 +114,7 @@ function createSubdomainAuth(config) {
|
|
|
143
114
|
}
|
|
144
115
|
async function createSessionToken(openId, options = {}) {
|
|
145
116
|
return signSession(
|
|
146
|
-
{ openId, appId:
|
|
117
|
+
{ openId, appId: serviceName, name: options.name || "" },
|
|
147
118
|
options
|
|
148
119
|
);
|
|
149
120
|
}
|
|
@@ -251,44 +222,6 @@ function createSubdomainAuth(config) {
|
|
|
251
222
|
portalUserId: portalUser.id
|
|
252
223
|
};
|
|
253
224
|
}
|
|
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
225
|
async function authenticateRequest(req) {
|
|
293
226
|
const cookie = await getCookie();
|
|
294
227
|
const cookies = req.headers.cookie ? new Map(Object.entries(cookie.parse(req.headers.cookie))) : /* @__PURE__ */ new Map();
|
|
@@ -339,26 +272,10 @@ function createSubdomainAuth(config) {
|
|
|
339
272
|
user = await db.getUserByOpenId(sessionUserId) ?? null;
|
|
340
273
|
}
|
|
341
274
|
} catch (error) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
}
|
|
275
|
+
console.error(`${tag} Failed to sync user info:`, error);
|
|
276
|
+
throw Object.assign(new Error("Failed to sync user info"), {
|
|
277
|
+
statusCode: 403
|
|
278
|
+
});
|
|
362
279
|
}
|
|
363
280
|
}
|
|
364
281
|
if (!user) {
|
|
@@ -391,62 +308,17 @@ function createSubdomainAuth(config) {
|
|
|
391
308
|
});
|
|
392
309
|
return user;
|
|
393
310
|
}
|
|
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
311
|
return {
|
|
436
312
|
verifySession,
|
|
437
313
|
createSessionToken,
|
|
438
314
|
authenticateRequest,
|
|
439
|
-
exchangeCodeForToken,
|
|
440
|
-
getUserInfo,
|
|
441
|
-
getUserInfoWithJwt,
|
|
442
315
|
getPortalUser,
|
|
443
316
|
getSessionCookieOptions,
|
|
444
|
-
isValidReturnUrl
|
|
445
|
-
registerOAuthRoutes
|
|
317
|
+
isValidReturnUrl
|
|
446
318
|
};
|
|
447
319
|
}
|
|
448
320
|
|
|
449
321
|
export {
|
|
450
322
|
createSubdomainAuth
|
|
451
323
|
};
|
|
452
|
-
//# sourceMappingURL=chunk-
|
|
324
|
+
//# sourceMappingURL=chunk-B63ROBSD.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 function getPortalClient() {\n if (portalClient) return portalClient;\n // Synchronous check — we only create if we have the URL\n if (!env.portalApiUrl) return null;\n // We need axios synchronously here, but it should already be loaded\n // by the time portal client is needed (after authenticateRequest calls getAxios)\n return null; // Will be created lazily in async context\n }\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 Secrets ────────────────────────────────────────────────\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 function getSessionSecret() {\n // Prefer portalJwtSecret for signing so cookies work cross-subdomain\n const secret = env.portalJwtSecret || env.jwtSecret;\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 Token ──────────────────────────────────────────────\n\n async function signSession(\n payload: SessionPayload,\n options: { expiresInMs?: number } = {}\n ): Promise<string> {\n const jose = await getJose();\n const issuedAt = Date.now();\n const expiresInMs = options.expiresInMs ?? sessionExpiryMs;\n const expirationSeconds = Math.floor((issuedAt + expiresInMs) / 1000);\n const secretKey = getSessionSecret();\n\n return new jose.SignJWT({\n openId: payload.openId,\n appId: payload.appId,\n name: payload.name,\n })\n .setProtectedHeader({ alg: \"HS256\", typ: \"JWT\" })\n .setExpirationTime(expirationSeconds)\n .sign(secretKey);\n }\n\n async function createSessionToken(\n openId: string,\n options: { expiresInMs?: number; name?: string } = {}\n ): Promise<string> {\n return signSession(\n { openId, appId: serviceName, name: options.name || \"\" },\n options\n );\n }\n\n // ── Session Verification ───────────────────────────────────────\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 PORTAL_JWT_SECRET first (cross-subdomain SSO)\n if (env.portalJwtSecret) {\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 {\n // Portal secret didn't work — fall through to local secret\n }\n }\n\n // Fallback: try local JWT_SECRET\n try {\n const secretKey = getSessionSecret();\n const { payload } = await jose.jwtVerify(cookieValue, secretKey, {\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} Session 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 createSessionToken,\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,
|
|
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
|
|
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.
|
|
@@ -66,8 +35,11 @@ interface TokenCostRate {
|
|
|
66
35
|
outputPer1k: number;
|
|
67
36
|
}
|
|
68
37
|
/**
|
|
69
|
-
* Default rate
|
|
70
|
-
*
|
|
38
|
+
* Default rate (USD per 1k tokens). Was priced for gemini-2.5-flash;
|
|
39
|
+
* llm.ts's MODEL moved to gemini-3.5-flash (2.5-flash was deprecated for new
|
|
40
|
+
* API keys) but this rate was NOT re-verified against 3.5-flash's actual
|
|
41
|
+
* published pricing — treat cost figures as stale until confirmed.
|
|
42
|
+
* Override per app via createLLMClient options once real pricing is known.
|
|
71
43
|
*/
|
|
72
44
|
declare const DEFAULT_COST_RATE: TokenCostRate;
|
|
73
45
|
/** Per-call context that varies request-to-request (set via InvokeParams.meta). */
|
|
@@ -212,7 +184,7 @@ type InvokeParams = {
|
|
|
212
184
|
type LLMClientOptions = {
|
|
213
185
|
/** App identifier recorded on every usage event, e.g. "marketing-site". */
|
|
214
186
|
app?: string;
|
|
215
|
-
/** Pricing used to estimate cost from tokens. Defaults to gemini-
|
|
187
|
+
/** Pricing used to estimate cost from tokens. Defaults to gemini-3.5-flash (rate unverified, see metering.ts). */
|
|
216
188
|
costRate?: TokenCostRate;
|
|
217
189
|
/** Metering sink — called after each successful call. Errors are swallowed. */
|
|
218
190
|
onUsage?: (event: UsageEvent) => void | Promise<void>;
|
|
@@ -248,102 +220,6 @@ type InvokeResult = {
|
|
|
248
220
|
};
|
|
249
221
|
declare function createLLMClient(env: CoreEnv, options?: LLMClientOptions): (params: InvokeParams) => Promise<InvokeResult>;
|
|
250
222
|
|
|
251
|
-
/**
|
|
252
|
-
* Image generation helper using internal ImageService.
|
|
253
|
-
*
|
|
254
|
-
* Example:
|
|
255
|
-
* import { storagePut } from "../storage";
|
|
256
|
-
* const generateImage = createImageGenerator(ENV, storagePut);
|
|
257
|
-
* const { url } = await generateImage({ prompt: "A serene landscape" });
|
|
258
|
-
*/
|
|
259
|
-
|
|
260
|
-
type GenerateImageOptions = {
|
|
261
|
-
prompt: string;
|
|
262
|
-
originalImages?: Array<{
|
|
263
|
-
url?: string;
|
|
264
|
-
b64Json?: string;
|
|
265
|
-
mimeType?: string;
|
|
266
|
-
}>;
|
|
267
|
-
};
|
|
268
|
-
type GenerateImageResponse = {
|
|
269
|
-
url?: string;
|
|
270
|
-
};
|
|
271
|
-
declare function createImageGenerator(env: CoreEnv, storagePut: StoragePutFn): (options: GenerateImageOptions) => Promise<GenerateImageResponse>;
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Google Maps API Integration for Manus WebDev Templates.
|
|
275
|
-
*
|
|
276
|
-
* Main function: createMapClient(env) returns makeRequest<T>(endpoint, params)
|
|
277
|
-
* All credentials are automatically injected. Array parameters use | as separator.
|
|
278
|
-
*
|
|
279
|
-
* Example:
|
|
280
|
-
* const makeRequest = createMapClient(ENV);
|
|
281
|
-
* const result = await makeRequest<GeocodeResult>(
|
|
282
|
-
* "/maps/api/geocode/json",
|
|
283
|
-
* { address: "1600 Amphitheatre Parkway" }
|
|
284
|
-
* );
|
|
285
|
-
*/
|
|
286
|
-
|
|
287
|
-
type MapRequestParams = Record<string, string | number | boolean | string[] | undefined>;
|
|
288
|
-
declare function createMapClient(env: CoreEnv): <T = unknown>(endpoint: string, params?: MapRequestParams) => Promise<T>;
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Owner notification helper — pushes operational updates to the Manus project owner.
|
|
292
|
-
*
|
|
293
|
-
* Example:
|
|
294
|
-
* const notifyOwner = createNotificationClient(ENV);
|
|
295
|
-
* await notifyOwner({ title: "New submission", content: "User X submitted form Y" });
|
|
296
|
-
*/
|
|
297
|
-
|
|
298
|
-
type NotificationPayload = {
|
|
299
|
-
title: string;
|
|
300
|
-
content: string;
|
|
301
|
-
};
|
|
302
|
-
declare function createNotificationClient(env: CoreEnv): (payload: NotificationPayload) => Promise<boolean>;
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Voice transcription helper using internal Speech-to-Text service (Whisper API).
|
|
306
|
-
*
|
|
307
|
-
* Example:
|
|
308
|
-
* const transcribeAudio = createTranscriptionClient(ENV);
|
|
309
|
-
* const result = await transcribeAudio({
|
|
310
|
-
* audioUrl: "https://storage.example.com/audio/recording.mp3",
|
|
311
|
-
* language: "en",
|
|
312
|
-
* });
|
|
313
|
-
*/
|
|
314
|
-
|
|
315
|
-
type TranscribeOptions = {
|
|
316
|
-
audioUrl: string;
|
|
317
|
-
language?: string;
|
|
318
|
-
prompt?: string;
|
|
319
|
-
};
|
|
320
|
-
type WhisperSegment = {
|
|
321
|
-
id: number;
|
|
322
|
-
seek: number;
|
|
323
|
-
start: number;
|
|
324
|
-
end: number;
|
|
325
|
-
text: string;
|
|
326
|
-
tokens: number[];
|
|
327
|
-
temperature: number;
|
|
328
|
-
avg_logprob: number;
|
|
329
|
-
compression_ratio: number;
|
|
330
|
-
no_speech_prob: number;
|
|
331
|
-
};
|
|
332
|
-
type WhisperResponse = {
|
|
333
|
-
task: string;
|
|
334
|
-
language: string;
|
|
335
|
-
duration: number;
|
|
336
|
-
text: string;
|
|
337
|
-
segments: WhisperSegment[];
|
|
338
|
-
};
|
|
339
|
-
type TranscriptionError = {
|
|
340
|
-
error: string;
|
|
341
|
-
code: string;
|
|
342
|
-
details: string;
|
|
343
|
-
};
|
|
344
|
-
type TranscriptionResult = WhisperResponse | TranscriptionError;
|
|
345
|
-
declare function createTranscriptionClient(env: CoreEnv): (options: TranscribeOptions) => Promise<TranscriptionResult>;
|
|
346
|
-
|
|
347
223
|
/**
|
|
348
224
|
* Personas for the shared Oplytics AI assistant.
|
|
349
225
|
*
|
|
@@ -454,4 +330,4 @@ declare function createSupportEngine(env: CoreEnv, options?: SupportEngineOption
|
|
|
454
330
|
};
|
|
455
331
|
type SupportEngine = ReturnType<typeof createSupportEngine>;
|
|
456
332
|
|
|
457
|
-
export { type AssistantMode, type BuildSystemPromptParams, type CallMeta, type ChatMessage, type ChatParams, type ChatResult, type CoreEnv, DEFAULT_COST_RATE, type
|
|
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 };
|