@lark-apaas/express-core 1.0.2-alpha.4 → 1.0.2-alpha.5
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/index.cjs +1 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -259,23 +259,12 @@ function mapToWindowEnvironment(input) {
|
|
|
259
259
|
return "online";
|
|
260
260
|
}
|
|
261
261
|
__name(mapToWindowEnvironment, "mapToWindowEnvironment");
|
|
262
|
-
var CUSTOM_DOMAINS = new Set((process.env.CUSTOM_DOMAINS || "").split(",").map((d) => d.trim()).filter(Boolean));
|
|
263
|
-
function resolveBasename(req, appId) {
|
|
264
|
-
const headerValue = req.headers["x-app-basename"];
|
|
265
|
-
if (typeof headerValue === "string" && headerValue) return headerValue;
|
|
266
|
-
if (CUSTOM_DOMAINS.size > 0 && CUSTOM_DOMAINS.has(req.hostname)) {
|
|
267
|
-
return "/";
|
|
268
|
-
}
|
|
269
|
-
return appId ? `/app/${appId}` : "/";
|
|
270
|
-
}
|
|
271
|
-
__name(resolveBasename, "resolveBasename");
|
|
272
262
|
function createViewContextMiddleware() {
|
|
273
263
|
return /* @__PURE__ */ __name(async function viewContextMiddleware(req, res, next) {
|
|
274
264
|
const { userId, tenantId, appId, loginUrl, userType } = req.userContext;
|
|
275
265
|
const csrfToken = req.csrfToken;
|
|
276
266
|
const appInfo = await getAppInfo(appId);
|
|
277
267
|
const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);
|
|
278
|
-
const basename = resolveBasename(req, appId);
|
|
279
268
|
req.__platform_data__ = {
|
|
280
269
|
csrfToken: csrfToken ?? "",
|
|
281
270
|
userId: userId ?? "",
|
|
@@ -286,8 +275,7 @@ function createViewContextMiddleware() {
|
|
|
286
275
|
loginUrl: loginUrl ?? "",
|
|
287
276
|
userType: userType ?? "",
|
|
288
277
|
tenantId,
|
|
289
|
-
environment
|
|
290
|
-
basename
|
|
278
|
+
environment
|
|
291
279
|
};
|
|
292
280
|
res.locals = {
|
|
293
281
|
...res.locals ?? {},
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/middlewares/user-context.ts","../src/middlewares/csrf-token.ts","../src/middlewares/csrf-validation.ts","../src/utils/platform-client.ts","../src/utils/safe-escape.ts","../src/middlewares/view-context.ts"],"sourcesContent":["import './types/express.d';\n\nexport { setup, db } from './setup';\nexport type { SetupConfig } from './types/config';\nexport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nexport { safeEscape, safeStringify } from './utils/safe-escape';\n\nexport { userContextMiddleware } from './middlewares/user-context';\nexport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nexport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nexport { createViewContextMiddleware } from './middlewares/view-context';\nexport type { CsrfTokenOptions } from './middlewares/csrf-token';\nexport type { CsrfValidationOptions } from './middlewares/csrf-validation';\n","/* eslint-disable */\nimport express from 'express';\nimport cookieParser from 'cookie-parser';\nimport { createDatabase, createRlsMiddleware } from '@lark-apaas/express-datapaas';\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nimport type { SetupConfig } from './types/config';\nimport { userContextMiddleware } from './middlewares/user-context';\nimport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nimport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nimport { createViewContextMiddleware } from './middlewares/view-context';\nimport { createPlatformClient } from './utils/platform-client';\n\n// eslint-disable-next-line import/no-mutable-exports\nexport let db: PostgresJsDatabase;\n\nexport function setup(app: express.Application, config?: SetupConfig): { close: () => Promise<void> } {\n const bodyLimit = config?.bodyLimit ?? process.env.BODY_SIZE_LIMIT ?? '1mb';\n\n // 1. Body parsers\n app.use(express.json({ limit: bodyLimit }));\n app.use(express.urlencoded({ limit: bodyLimit, extended: true }));\n\n // 2. Cookie parser\n app.use(cookieParser());\n\n // 3. UserContext (all routes)\n app.use(userContextMiddleware);\n\n // 4. Database + RLS\n const connectionString = config?.database?.connectionString ?? process.env.FORCE_DB_CONNECT_URL ?? '';\n const { db: database, close } = createDatabase({\n connectionString,\n ...config?.database,\n });\n db = database;\n app.use(createRlsMiddleware());\n\n // 5. CSRF\n const csrfEnabled = config?.csrf?.enabled ?? true;\n if (csrfEnabled) {\n const csrfRoutes = config?.csrf?.routes ?? ['/api/{*path}'];\n\n // 页面路由生成 token(排除 API 路由),为后续 API 请求准备 cookie\n app.use(createCsrfTokenMiddleware({\n cookieKey: config?.csrf?.cookieKey,\n cookieMaxAge: config?.csrf?.cookieMaxAge,\n cookiePath: config?.csrf?.cookiePath,\n exclude: csrfRoutes,\n }));\n\n // API 路由验证 token(cookie vs header)\n app.use(csrfRoutes, createCsrfValidationMiddleware({\n headerKey: config?.csrf?.headerKey,\n cookieKey: config?.csrf?.cookieKey,\n }));\n }\n\n // 6. Platform client + ViewContext\n createPlatformClient();\n app.use(createViewContextMiddleware());\n\n return { close };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nconst SUDA_WEB_USER_HEADER_KEY = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n login_url: string;\n user_type: string;\n env: 'preview' | 'runtime';\n user_name: Record<string, string>;\n is_system_account?: boolean;\n roles?: string[];\n}\n\nfunction getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[SUDA_WEB_USER_HEADER_KEY] as string | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n return JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n}\n\nexport function userContextMiddleware(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n loginUrl: webUser?.login_url ?? '',\n userType: webUser?.user_type ?? '',\n env: webUser?.env ?? 'runtime',\n userName: webUser?.user_name?.zh_cn ?? '',\n userNameEn: webUser?.user_name?.en_us ?? '',\n userNameI18n: webUser?.user_name ?? {},\n isSystemAccount: webUser?.is_system_account ?? false,\n roles: webUser?.roles,\n };\n next();\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport crypto from 'crypto';\n\nexport interface CsrfTokenOptions {\n cookieKey?: string;\n cookieMaxAge?: number;\n cookiePath?: string;\n /** 排除的路由模式,匹配到则跳过 token 生成 */\n exclude?: string[];\n}\n\ninterface ResolvedCsrfTokenOptions {\n cookieKey: string;\n cookieMaxAge: number;\n cookiePath: string;\n exclude: RegExp[];\n}\n\nfunction pathToRegex(pattern: string): RegExp {\n // 将路径模式转为正则:/api/{*path} → /^\\/api\\/.*$/, /api/* → /^\\/api\\/.*$/\n const normalized = pattern.replace(/\\{[*][\\w]*\\}/g, '*');\n const escaped = normalized\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n}\n\nfunction resolveOptions(options: CsrfTokenOptions): ResolvedCsrfTokenOptions {\n return {\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 * 60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n exclude: (options.exclude ?? []).map(pathToRegex),\n };\n}\n\nfunction genToken(): string {\n const ts = Math.floor(Date.now() / 1000);\n const randInt64 = BigInt('0x' + crypto.randomBytes(8).toString('hex')).toString();\n const s = `${randInt64}.${ts}`;\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n return `${sha1.digest('hex')}-${ts}`;\n}\n\nexport function createCsrfTokenMiddleware(options?: CsrfTokenOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {\n // 匹配排除路由则跳过\n const pathname = req.path;\n if (resolved.exclude.some(re => re.test(pathname))) {\n return next();\n }\n\n const { cookieKey, cookieMaxAge, cookiePath } = resolved;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n\n if (originToken) {\n req.csrfToken = originToken;\n } else {\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: false,\n secure: true,\n sameSite: 'none',\n partitioned: true,\n });\n }\n next();\n };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nexport interface CsrfValidationOptions {\n headerKey?: string;\n cookieKey?: string;\n}\n\ninterface ResolvedCsrfValidationOptions {\n headerKey: string;\n cookieKey: string;\n}\n\nfunction resolveOptions(options: CsrfValidationOptions): ResolvedCsrfValidationOptions {\n return {\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nfunction sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n\nexport function createCsrfValidationMiddleware(options?: CsrfValidationOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfValidationMiddleware(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = resolved;\n\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n\n next();\n };\n}\n","import { HttpClient } from '@lark-apaas/http-client';\n\nlet _client: HttpClient | null = null;\n\nexport function createPlatformClient(): HttpClient {\n if (_client) return _client;\n _client = new HttpClient({\n timeout: 5000,\n platform: { enabled: true },\n });\n return _client;\n}\n\nexport function getPlatformClient(): HttpClient {\n if (!_client) {\n throw new Error('Platform client not initialized. Call setup() first.');\n }\n return _client;\n}\n","/**\n * 安全转义的 Json Stringify,模版渲染的 JSON 对象需要调用 safeStringify,而不是用原生的 JSON.stringify。\n * 抄 gson 的实现: https://github.com/google/gson/blob/49ddab9eeb6cbac686711deca6001d40e8d8500d/gson/src/main/java/com/google/gson/stream/JsonWriter.java\n * 将 json 的敏感字符转换为 unicode, 避免 xss\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeStringify(obj: any) {\n const json = JSON.stringify(obj);\n return safeEscape(json);\n}\n\nenum EscapedChar {\n LT = 0x3c, // <\n GT = 0x3e, // >\n AMP = 0x26, // &\n APO = 0x27, // '\n QUOT = 0x22, // \"\n EQUAL = 0x3d, // =\n LF = 0x0a, // \\n\n CR = 0x0d, // \\r\n LS = 0x2028, // 行分隔符\n PS = 0x2029, // 段落分隔符\n}\n\nexport function safeEscape(s: string) {\n return s.replace(/[<>&='\"\\n\\r\\u2028\\u2029]/g, function (c) {\n switch (c.charCodeAt(0)) {\n case EscapedChar.LT:\n return '\\\\u003c';\n case EscapedChar.GT:\n return '\\\\u003e';\n case EscapedChar.AMP:\n return '\\\\u0026';\n case EscapedChar.EQUAL:\n return '\\\\u003d';\n case EscapedChar.APO:\n return '\\\\u0027';\n case EscapedChar.QUOT:\n return '\\\\u0022';\n case EscapedChar.LF:\n return '\\\\u000a';\n case EscapedChar.CR:\n return '\\\\u000d';\n case EscapedChar.LS:\n return '\\\\u2028';\n case EscapedChar.PS:\n return '\\\\u2029';\n default:\n return c;\n }\n });\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport { getPlatformClient } from '../utils/platform-client';\nimport { safeEscape } from '../utils/safe-escape';\n\ninterface GetAppInfoResponse {\n status_code: string;\n data: {\n status_code: number;\n app_info: {\n app_name: string;\n app_description: string;\n app_avatar: string;\n };\n app_runtime_extra: {\n url: string;\n bucket: string;\n };\n };\n}\n\nasync function getAppInfo(appId?: string) {\n if (!appId) {\n console.warn('appId is empty, skip get app info');\n return null;\n }\n try {\n const client = getPlatformClient();\n const resp = await client.get(`/b/${appId}/get_published_v2`);\n if (resp.status !== 200) {\n throw new Error(`Failed to get app info, status: ${resp.status}`);\n }\n const data = (await resp.json()) as GetAppInfoResponse;\n if (data.status_code !== '0') {\n throw new Error(`Failed to get app info, status_code: ${data.status_code}`);\n }\n return data.data.app_info ?? {};\n } catch (err) {\n console.error('Failed to get app info', err);\n return null;\n }\n}\n\nfunction mapToWindowEnvironment(input?: string): string {\n const value = (input || '').trim().toLowerCase();\n if (value === 'boe') return 'staging';\n if (value === 'pre') return 'gray';\n return 'online';\n}\n\n// CUSTOM_DOMAINS env 仅在进程启动时读一次(性能考量)\n// 改 env 需重启进程\nconst CUSTOM_DOMAINS = new Set(\n (process.env.CUSTOM_DOMAINS || '')\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n);\n\n// basename 优先级:\n// 1. X-App-Basename header(DomainGate 注入,正式上线后的主要来源)\n// 2. host 匹配 CUSTOM_DOMAINS env(demo / 自部署场景,启动时通过 env 列出自定义域名 → basename=/)\n// 3. /app/${appId}(默认域名场景的兜底,跟现状一致)\n//\n// 注意:req.hostname 在 trust proxy 场景下从 X-Forwarded-Host 解析。\n// demo 期 a.com 直接到应用 server(无 proxy),行为可预期。\n// 正式接 DomainGate 后走 header 兜底(第 1 层),env 自动失效,无需再依赖 req.hostname。\nfunction resolveBasename(req: Request, appId?: string): string {\n const headerValue = req.headers['x-app-basename'];\n if (typeof headerValue === 'string' && headerValue) return headerValue;\n\n if (CUSTOM_DOMAINS.size > 0 && CUSTOM_DOMAINS.has(req.hostname)) {\n return '/';\n }\n\n // appId 缺失时不构造伪 basename(避免 \"/app/undefined\"),降级到根路径\n return appId ? `/app/${appId}` : '/';\n}\n\nexport function createViewContextMiddleware() {\n return async function viewContextMiddleware(req: Request, res: Response, next: NextFunction) {\n const { userId, tenantId, appId, loginUrl, userType } = req.userContext;\n const csrfToken = req.csrfToken;\n const appInfo = await getAppInfo(appId);\n const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);\n const basename = resolveBasename(req, appId);\n\n req.__platform_data__ = {\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n tenantId,\n environment,\n basename,\n };\n\n res.locals = {\n ...(res.locals ?? {}),\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n tenantId: tenantId ?? '',\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n environment,\n };\n\n next();\n };\n}\n"],"mappings":"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","names":["SUDA_WEB_USER_HEADER_KEY","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","JSON","parse","err","console","error","userContextMiddleware","_res","next","webUser","userContext","userId","user_id","tenantId","tenant_id","appId","app_id","loginUrl","login_url","userType","user_type","env","userName","user_name","zh_cn","userNameEn","en_us","userNameI18n","isSystemAccount","is_system_account","roles","pathToRegex","pattern","normalized","replace","escaped","RegExp","resolveOptions","options","cookieKey","cookieMaxAge","cookiePath","exclude","map","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","createCsrfTokenMiddleware","resolved","csrfTokenMiddleware","req","res","next","pathname","path","some","re","test","originToken","cookies","toLowerCase","csrfToken","token","cookie","maxAge","httpOnly","secure","sameSite","partitioned","resolveOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","createCsrfValidationMiddleware","resolved","csrfValidationMiddleware","req","next","cookieCsrfToken","cookies","toLowerCase","headerCsrfToken","headers","_client","createPlatformClient","HttpClient","timeout","platform","enabled","getPlatformClient","Error","safeStringify","obj","json","JSON","stringify","safeEscape","safeEscape","s","replace","c","charCodeAt","getAppInfo","appId","console","warn","client","getPlatformClient","resp","get","status","Error","data","json","status_code","app_info","err","error","mapToWindowEnvironment","input","value","trim","toLowerCase","CUSTOM_DOMAINS","Set","process","env","split","map","d","filter","Boolean","resolveBasename","req","headerValue","headers","size","has","hostname","createViewContextMiddleware","viewContextMiddleware","res","next","userId","tenantId","loginUrl","userType","userContext","csrfToken","appInfo","environment","FORCE_FRAMEWORK_ENVIRONMENT","basename","__platform_data__","appName","safeEscape","app_name","appAvatar","app_avatar","appDescription","app_description","locals","db","setup","app","config","bodyLimit","process","env","BODY_SIZE_LIMIT","use","express","json","limit","urlencoded","extended","cookieParser","userContextMiddleware","connectionString","database","FORCE_DB_CONNECT_URL","close","createDatabase","createRlsMiddleware","csrfEnabled","csrf","enabled","csrfRoutes","routes","createCsrfTokenMiddleware","cookieKey","cookieMaxAge","cookiePath","exclude","createCsrfValidationMiddleware","headerKey","createPlatformClient","createViewContextMiddleware"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/middlewares/user-context.ts","../src/middlewares/csrf-token.ts","../src/middlewares/csrf-validation.ts","../src/utils/platform-client.ts","../src/utils/safe-escape.ts","../src/middlewares/view-context.ts"],"sourcesContent":["import './types/express.d';\n\nexport { setup, db } from './setup';\nexport type { SetupConfig } from './types/config';\nexport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nexport { safeEscape, safeStringify } from './utils/safe-escape';\n\nexport { userContextMiddleware } from './middlewares/user-context';\nexport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nexport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nexport { createViewContextMiddleware } from './middlewares/view-context';\nexport type { CsrfTokenOptions } from './middlewares/csrf-token';\nexport type { CsrfValidationOptions } from './middlewares/csrf-validation';\n","/* eslint-disable */\nimport express from 'express';\nimport cookieParser from 'cookie-parser';\nimport { createDatabase, createRlsMiddleware } from '@lark-apaas/express-datapaas';\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nimport type { SetupConfig } from './types/config';\nimport { userContextMiddleware } from './middlewares/user-context';\nimport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nimport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nimport { createViewContextMiddleware } from './middlewares/view-context';\nimport { createPlatformClient } from './utils/platform-client';\n\n// eslint-disable-next-line import/no-mutable-exports\nexport let db: PostgresJsDatabase;\n\nexport function setup(app: express.Application, config?: SetupConfig): { close: () => Promise<void> } {\n const bodyLimit = config?.bodyLimit ?? process.env.BODY_SIZE_LIMIT ?? '1mb';\n\n // 1. Body parsers\n app.use(express.json({ limit: bodyLimit }));\n app.use(express.urlencoded({ limit: bodyLimit, extended: true }));\n\n // 2. Cookie parser\n app.use(cookieParser());\n\n // 3. UserContext (all routes)\n app.use(userContextMiddleware);\n\n // 4. Database + RLS\n const connectionString = config?.database?.connectionString ?? process.env.FORCE_DB_CONNECT_URL ?? '';\n const { db: database, close } = createDatabase({\n connectionString,\n ...config?.database,\n });\n db = database;\n app.use(createRlsMiddleware());\n\n // 5. CSRF\n const csrfEnabled = config?.csrf?.enabled ?? true;\n if (csrfEnabled) {\n const csrfRoutes = config?.csrf?.routes ?? ['/api/{*path}'];\n\n // 页面路由生成 token(排除 API 路由),为后续 API 请求准备 cookie\n app.use(createCsrfTokenMiddleware({\n cookieKey: config?.csrf?.cookieKey,\n cookieMaxAge: config?.csrf?.cookieMaxAge,\n cookiePath: config?.csrf?.cookiePath,\n exclude: csrfRoutes,\n }));\n\n // API 路由验证 token(cookie vs header)\n app.use(csrfRoutes, createCsrfValidationMiddleware({\n headerKey: config?.csrf?.headerKey,\n cookieKey: config?.csrf?.cookieKey,\n }));\n }\n\n // 6. Platform client + ViewContext\n createPlatformClient();\n app.use(createViewContextMiddleware());\n\n return { close };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nconst SUDA_WEB_USER_HEADER_KEY = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n login_url: string;\n user_type: string;\n env: 'preview' | 'runtime';\n user_name: Record<string, string>;\n is_system_account?: boolean;\n roles?: string[];\n}\n\nfunction getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[SUDA_WEB_USER_HEADER_KEY] as string | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n return JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n}\n\nexport function userContextMiddleware(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n loginUrl: webUser?.login_url ?? '',\n userType: webUser?.user_type ?? '',\n env: webUser?.env ?? 'runtime',\n userName: webUser?.user_name?.zh_cn ?? '',\n userNameEn: webUser?.user_name?.en_us ?? '',\n userNameI18n: webUser?.user_name ?? {},\n isSystemAccount: webUser?.is_system_account ?? false,\n roles: webUser?.roles,\n };\n next();\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport crypto from 'crypto';\n\nexport interface CsrfTokenOptions {\n cookieKey?: string;\n cookieMaxAge?: number;\n cookiePath?: string;\n /** 排除的路由模式,匹配到则跳过 token 生成 */\n exclude?: string[];\n}\n\ninterface ResolvedCsrfTokenOptions {\n cookieKey: string;\n cookieMaxAge: number;\n cookiePath: string;\n exclude: RegExp[];\n}\n\nfunction pathToRegex(pattern: string): RegExp {\n // 将路径模式转为正则:/api/{*path} → /^\\/api\\/.*$/, /api/* → /^\\/api\\/.*$/\n const normalized = pattern.replace(/\\{[*][\\w]*\\}/g, '*');\n const escaped = normalized\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n}\n\nfunction resolveOptions(options: CsrfTokenOptions): ResolvedCsrfTokenOptions {\n return {\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 * 60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n exclude: (options.exclude ?? []).map(pathToRegex),\n };\n}\n\nfunction genToken(): string {\n const ts = Math.floor(Date.now() / 1000);\n const randInt64 = BigInt('0x' + crypto.randomBytes(8).toString('hex')).toString();\n const s = `${randInt64}.${ts}`;\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n return `${sha1.digest('hex')}-${ts}`;\n}\n\nexport function createCsrfTokenMiddleware(options?: CsrfTokenOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {\n // 匹配排除路由则跳过\n const pathname = req.path;\n if (resolved.exclude.some(re => re.test(pathname))) {\n return next();\n }\n\n const { cookieKey, cookieMaxAge, cookiePath } = resolved;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n\n if (originToken) {\n req.csrfToken = originToken;\n } else {\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: false,\n secure: true,\n sameSite: 'none',\n partitioned: true,\n });\n }\n next();\n };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nexport interface CsrfValidationOptions {\n headerKey?: string;\n cookieKey?: string;\n}\n\ninterface ResolvedCsrfValidationOptions {\n headerKey: string;\n cookieKey: string;\n}\n\nfunction resolveOptions(options: CsrfValidationOptions): ResolvedCsrfValidationOptions {\n return {\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nfunction sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n\nexport function createCsrfValidationMiddleware(options?: CsrfValidationOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfValidationMiddleware(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = resolved;\n\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n\n next();\n };\n}\n","import { HttpClient } from '@lark-apaas/http-client';\n\nlet _client: HttpClient | null = null;\n\nexport function createPlatformClient(): HttpClient {\n if (_client) return _client;\n _client = new HttpClient({\n timeout: 5000,\n platform: { enabled: true },\n });\n return _client;\n}\n\nexport function getPlatformClient(): HttpClient {\n if (!_client) {\n throw new Error('Platform client not initialized. Call setup() first.');\n }\n return _client;\n}\n","/**\n * 安全转义的 Json Stringify,模版渲染的 JSON 对象需要调用 safeStringify,而不是用原生的 JSON.stringify。\n * 抄 gson 的实现: https://github.com/google/gson/blob/49ddab9eeb6cbac686711deca6001d40e8d8500d/gson/src/main/java/com/google/gson/stream/JsonWriter.java\n * 将 json 的敏感字符转换为 unicode, 避免 xss\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeStringify(obj: any) {\n const json = JSON.stringify(obj);\n return safeEscape(json);\n}\n\nenum EscapedChar {\n LT = 0x3c, // <\n GT = 0x3e, // >\n AMP = 0x26, // &\n APO = 0x27, // '\n QUOT = 0x22, // \"\n EQUAL = 0x3d, // =\n LF = 0x0a, // \\n\n CR = 0x0d, // \\r\n LS = 0x2028, // 行分隔符\n PS = 0x2029, // 段落分隔符\n}\n\nexport function safeEscape(s: string) {\n return s.replace(/[<>&='\"\\n\\r\\u2028\\u2029]/g, function (c) {\n switch (c.charCodeAt(0)) {\n case EscapedChar.LT:\n return '\\\\u003c';\n case EscapedChar.GT:\n return '\\\\u003e';\n case EscapedChar.AMP:\n return '\\\\u0026';\n case EscapedChar.EQUAL:\n return '\\\\u003d';\n case EscapedChar.APO:\n return '\\\\u0027';\n case EscapedChar.QUOT:\n return '\\\\u0022';\n case EscapedChar.LF:\n return '\\\\u000a';\n case EscapedChar.CR:\n return '\\\\u000d';\n case EscapedChar.LS:\n return '\\\\u2028';\n case EscapedChar.PS:\n return '\\\\u2029';\n default:\n return c;\n }\n });\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport { getPlatformClient } from '../utils/platform-client';\nimport { safeEscape } from '../utils/safe-escape';\n\ninterface GetAppInfoResponse {\n status_code: string;\n data: {\n status_code: number;\n app_info: {\n app_name: string;\n app_description: string;\n app_avatar: string;\n };\n app_runtime_extra: {\n url: string;\n bucket: string;\n };\n };\n}\n\nasync function getAppInfo(appId?: string) {\n if (!appId) {\n console.warn('appId is empty, skip get app info');\n return null;\n }\n try {\n const client = getPlatformClient();\n const resp = await client.get(`/b/${appId}/get_published_v2`);\n if (resp.status !== 200) {\n throw new Error(`Failed to get app info, status: ${resp.status}`);\n }\n const data = (await resp.json()) as GetAppInfoResponse;\n if (data.status_code !== '0') {\n throw new Error(`Failed to get app info, status_code: ${data.status_code}`);\n }\n return data.data.app_info ?? {};\n } catch (err) {\n console.error('Failed to get app info', err);\n return null;\n }\n}\n\nfunction mapToWindowEnvironment(input?: string): string {\n const value = (input || '').trim().toLowerCase();\n if (value === 'boe') return 'staging';\n if (value === 'pre') return 'gray';\n return 'online';\n}\n\nexport function createViewContextMiddleware() {\n return async function viewContextMiddleware(req: Request, res: Response, next: NextFunction) {\n const { userId, tenantId, appId, loginUrl, userType } = req.userContext;\n const csrfToken = req.csrfToken;\n const appInfo = await getAppInfo(appId);\n const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);\n\n req.__platform_data__ = {\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n tenantId,\n environment,\n };\n\n res.locals = {\n ...(res.locals ?? {}),\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n tenantId: tenantId ?? '',\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n environment,\n };\n\n next();\n };\n}\n"],"mappings":"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","names":["SUDA_WEB_USER_HEADER_KEY","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","JSON","parse","err","console","error","userContextMiddleware","_res","next","webUser","userContext","userId","user_id","tenantId","tenant_id","appId","app_id","loginUrl","login_url","userType","user_type","env","userName","user_name","zh_cn","userNameEn","en_us","userNameI18n","isSystemAccount","is_system_account","roles","pathToRegex","pattern","normalized","replace","escaped","RegExp","resolveOptions","options","cookieKey","cookieMaxAge","cookiePath","exclude","map","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","createCsrfTokenMiddleware","resolved","csrfTokenMiddleware","req","res","next","pathname","path","some","re","test","originToken","cookies","toLowerCase","csrfToken","token","cookie","maxAge","httpOnly","secure","sameSite","partitioned","resolveOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","createCsrfValidationMiddleware","resolved","csrfValidationMiddleware","req","next","cookieCsrfToken","cookies","toLowerCase","headerCsrfToken","headers","_client","createPlatformClient","HttpClient","timeout","platform","enabled","getPlatformClient","Error","safeStringify","obj","json","JSON","stringify","safeEscape","safeEscape","s","replace","c","charCodeAt","getAppInfo","appId","console","warn","client","getPlatformClient","resp","get","status","Error","data","json","status_code","app_info","err","error","mapToWindowEnvironment","input","value","trim","toLowerCase","createViewContextMiddleware","viewContextMiddleware","req","res","next","userId","tenantId","loginUrl","userType","userContext","csrfToken","appInfo","environment","process","env","FORCE_FRAMEWORK_ENVIRONMENT","__platform_data__","appName","safeEscape","app_name","appAvatar","app_avatar","appDescription","app_description","locals","db","setup","app","config","bodyLimit","process","env","BODY_SIZE_LIMIT","use","express","json","limit","urlencoded","extended","cookieParser","userContextMiddleware","connectionString","database","FORCE_DB_CONNECT_URL","close","createDatabase","createRlsMiddleware","csrfEnabled","csrf","enabled","csrfRoutes","routes","createCsrfTokenMiddleware","cookieKey","cookieMaxAge","cookiePath","exclude","createCsrfValidationMiddleware","headerKey","createPlatformClient","createViewContextMiddleware"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -218,23 +218,12 @@ function mapToWindowEnvironment(input) {
|
|
|
218
218
|
return "online";
|
|
219
219
|
}
|
|
220
220
|
__name(mapToWindowEnvironment, "mapToWindowEnvironment");
|
|
221
|
-
var CUSTOM_DOMAINS = new Set((process.env.CUSTOM_DOMAINS || "").split(",").map((d) => d.trim()).filter(Boolean));
|
|
222
|
-
function resolveBasename(req, appId) {
|
|
223
|
-
const headerValue = req.headers["x-app-basename"];
|
|
224
|
-
if (typeof headerValue === "string" && headerValue) return headerValue;
|
|
225
|
-
if (CUSTOM_DOMAINS.size > 0 && CUSTOM_DOMAINS.has(req.hostname)) {
|
|
226
|
-
return "/";
|
|
227
|
-
}
|
|
228
|
-
return appId ? `/app/${appId}` : "/";
|
|
229
|
-
}
|
|
230
|
-
__name(resolveBasename, "resolveBasename");
|
|
231
221
|
function createViewContextMiddleware() {
|
|
232
222
|
return /* @__PURE__ */ __name(async function viewContextMiddleware(req, res, next) {
|
|
233
223
|
const { userId, tenantId, appId, loginUrl, userType } = req.userContext;
|
|
234
224
|
const csrfToken = req.csrfToken;
|
|
235
225
|
const appInfo = await getAppInfo(appId);
|
|
236
226
|
const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);
|
|
237
|
-
const basename = resolveBasename(req, appId);
|
|
238
227
|
req.__platform_data__ = {
|
|
239
228
|
csrfToken: csrfToken ?? "",
|
|
240
229
|
userId: userId ?? "",
|
|
@@ -245,8 +234,7 @@ function createViewContextMiddleware() {
|
|
|
245
234
|
loginUrl: loginUrl ?? "",
|
|
246
235
|
userType: userType ?? "",
|
|
247
236
|
tenantId,
|
|
248
|
-
environment
|
|
249
|
-
basename
|
|
237
|
+
environment
|
|
250
238
|
};
|
|
251
239
|
res.locals = {
|
|
252
240
|
...res.locals ?? {},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/setup.ts","../src/middlewares/user-context.ts","../src/middlewares/csrf-token.ts","../src/middlewares/csrf-validation.ts","../src/utils/platform-client.ts","../src/utils/safe-escape.ts","../src/middlewares/view-context.ts"],"sourcesContent":["/* eslint-disable */\nimport express from 'express';\nimport cookieParser from 'cookie-parser';\nimport { createDatabase, createRlsMiddleware } from '@lark-apaas/express-datapaas';\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nimport type { SetupConfig } from './types/config';\nimport { userContextMiddleware } from './middlewares/user-context';\nimport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nimport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nimport { createViewContextMiddleware } from './middlewares/view-context';\nimport { createPlatformClient } from './utils/platform-client';\n\n// eslint-disable-next-line import/no-mutable-exports\nexport let db: PostgresJsDatabase;\n\nexport function setup(app: express.Application, config?: SetupConfig): { close: () => Promise<void> } {\n const bodyLimit = config?.bodyLimit ?? process.env.BODY_SIZE_LIMIT ?? '1mb';\n\n // 1. Body parsers\n app.use(express.json({ limit: bodyLimit }));\n app.use(express.urlencoded({ limit: bodyLimit, extended: true }));\n\n // 2. Cookie parser\n app.use(cookieParser());\n\n // 3. UserContext (all routes)\n app.use(userContextMiddleware);\n\n // 4. Database + RLS\n const connectionString = config?.database?.connectionString ?? process.env.FORCE_DB_CONNECT_URL ?? '';\n const { db: database, close } = createDatabase({\n connectionString,\n ...config?.database,\n });\n db = database;\n app.use(createRlsMiddleware());\n\n // 5. CSRF\n const csrfEnabled = config?.csrf?.enabled ?? true;\n if (csrfEnabled) {\n const csrfRoutes = config?.csrf?.routes ?? ['/api/{*path}'];\n\n // 页面路由生成 token(排除 API 路由),为后续 API 请求准备 cookie\n app.use(createCsrfTokenMiddleware({\n cookieKey: config?.csrf?.cookieKey,\n cookieMaxAge: config?.csrf?.cookieMaxAge,\n cookiePath: config?.csrf?.cookiePath,\n exclude: csrfRoutes,\n }));\n\n // API 路由验证 token(cookie vs header)\n app.use(csrfRoutes, createCsrfValidationMiddleware({\n headerKey: config?.csrf?.headerKey,\n cookieKey: config?.csrf?.cookieKey,\n }));\n }\n\n // 6. Platform client + ViewContext\n createPlatformClient();\n app.use(createViewContextMiddleware());\n\n return { close };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nconst SUDA_WEB_USER_HEADER_KEY = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n login_url: string;\n user_type: string;\n env: 'preview' | 'runtime';\n user_name: Record<string, string>;\n is_system_account?: boolean;\n roles?: string[];\n}\n\nfunction getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[SUDA_WEB_USER_HEADER_KEY] as string | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n return JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n}\n\nexport function userContextMiddleware(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n loginUrl: webUser?.login_url ?? '',\n userType: webUser?.user_type ?? '',\n env: webUser?.env ?? 'runtime',\n userName: webUser?.user_name?.zh_cn ?? '',\n userNameEn: webUser?.user_name?.en_us ?? '',\n userNameI18n: webUser?.user_name ?? {},\n isSystemAccount: webUser?.is_system_account ?? false,\n roles: webUser?.roles,\n };\n next();\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport crypto from 'crypto';\n\nexport interface CsrfTokenOptions {\n cookieKey?: string;\n cookieMaxAge?: number;\n cookiePath?: string;\n /** 排除的路由模式,匹配到则跳过 token 生成 */\n exclude?: string[];\n}\n\ninterface ResolvedCsrfTokenOptions {\n cookieKey: string;\n cookieMaxAge: number;\n cookiePath: string;\n exclude: RegExp[];\n}\n\nfunction pathToRegex(pattern: string): RegExp {\n // 将路径模式转为正则:/api/{*path} → /^\\/api\\/.*$/, /api/* → /^\\/api\\/.*$/\n const normalized = pattern.replace(/\\{[*][\\w]*\\}/g, '*');\n const escaped = normalized\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n}\n\nfunction resolveOptions(options: CsrfTokenOptions): ResolvedCsrfTokenOptions {\n return {\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 * 60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n exclude: (options.exclude ?? []).map(pathToRegex),\n };\n}\n\nfunction genToken(): string {\n const ts = Math.floor(Date.now() / 1000);\n const randInt64 = BigInt('0x' + crypto.randomBytes(8).toString('hex')).toString();\n const s = `${randInt64}.${ts}`;\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n return `${sha1.digest('hex')}-${ts}`;\n}\n\nexport function createCsrfTokenMiddleware(options?: CsrfTokenOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {\n // 匹配排除路由则跳过\n const pathname = req.path;\n if (resolved.exclude.some(re => re.test(pathname))) {\n return next();\n }\n\n const { cookieKey, cookieMaxAge, cookiePath } = resolved;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n\n if (originToken) {\n req.csrfToken = originToken;\n } else {\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: false,\n secure: true,\n sameSite: 'none',\n partitioned: true,\n });\n }\n next();\n };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nexport interface CsrfValidationOptions {\n headerKey?: string;\n cookieKey?: string;\n}\n\ninterface ResolvedCsrfValidationOptions {\n headerKey: string;\n cookieKey: string;\n}\n\nfunction resolveOptions(options: CsrfValidationOptions): ResolvedCsrfValidationOptions {\n return {\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nfunction sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n\nexport function createCsrfValidationMiddleware(options?: CsrfValidationOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfValidationMiddleware(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = resolved;\n\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n\n next();\n };\n}\n","import { HttpClient } from '@lark-apaas/http-client';\n\nlet _client: HttpClient | null = null;\n\nexport function createPlatformClient(): HttpClient {\n if (_client) return _client;\n _client = new HttpClient({\n timeout: 5000,\n platform: { enabled: true },\n });\n return _client;\n}\n\nexport function getPlatformClient(): HttpClient {\n if (!_client) {\n throw new Error('Platform client not initialized. Call setup() first.');\n }\n return _client;\n}\n","/**\n * 安全转义的 Json Stringify,模版渲染的 JSON 对象需要调用 safeStringify,而不是用原生的 JSON.stringify。\n * 抄 gson 的实现: https://github.com/google/gson/blob/49ddab9eeb6cbac686711deca6001d40e8d8500d/gson/src/main/java/com/google/gson/stream/JsonWriter.java\n * 将 json 的敏感字符转换为 unicode, 避免 xss\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeStringify(obj: any) {\n const json = JSON.stringify(obj);\n return safeEscape(json);\n}\n\nenum EscapedChar {\n LT = 0x3c, // <\n GT = 0x3e, // >\n AMP = 0x26, // &\n APO = 0x27, // '\n QUOT = 0x22, // \"\n EQUAL = 0x3d, // =\n LF = 0x0a, // \\n\n CR = 0x0d, // \\r\n LS = 0x2028, // 行分隔符\n PS = 0x2029, // 段落分隔符\n}\n\nexport function safeEscape(s: string) {\n return s.replace(/[<>&='\"\\n\\r\\u2028\\u2029]/g, function (c) {\n switch (c.charCodeAt(0)) {\n case EscapedChar.LT:\n return '\\\\u003c';\n case EscapedChar.GT:\n return '\\\\u003e';\n case EscapedChar.AMP:\n return '\\\\u0026';\n case EscapedChar.EQUAL:\n return '\\\\u003d';\n case EscapedChar.APO:\n return '\\\\u0027';\n case EscapedChar.QUOT:\n return '\\\\u0022';\n case EscapedChar.LF:\n return '\\\\u000a';\n case EscapedChar.CR:\n return '\\\\u000d';\n case EscapedChar.LS:\n return '\\\\u2028';\n case EscapedChar.PS:\n return '\\\\u2029';\n default:\n return c;\n }\n });\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport { getPlatformClient } from '../utils/platform-client';\nimport { safeEscape } from '../utils/safe-escape';\n\ninterface GetAppInfoResponse {\n status_code: string;\n data: {\n status_code: number;\n app_info: {\n app_name: string;\n app_description: string;\n app_avatar: string;\n };\n app_runtime_extra: {\n url: string;\n bucket: string;\n };\n };\n}\n\nasync function getAppInfo(appId?: string) {\n if (!appId) {\n console.warn('appId is empty, skip get app info');\n return null;\n }\n try {\n const client = getPlatformClient();\n const resp = await client.get(`/b/${appId}/get_published_v2`);\n if (resp.status !== 200) {\n throw new Error(`Failed to get app info, status: ${resp.status}`);\n }\n const data = (await resp.json()) as GetAppInfoResponse;\n if (data.status_code !== '0') {\n throw new Error(`Failed to get app info, status_code: ${data.status_code}`);\n }\n return data.data.app_info ?? {};\n } catch (err) {\n console.error('Failed to get app info', err);\n return null;\n }\n}\n\nfunction mapToWindowEnvironment(input?: string): string {\n const value = (input || '').trim().toLowerCase();\n if (value === 'boe') return 'staging';\n if (value === 'pre') return 'gray';\n return 'online';\n}\n\n// CUSTOM_DOMAINS env 仅在进程启动时读一次(性能考量)\n// 改 env 需重启进程\nconst CUSTOM_DOMAINS = new Set(\n (process.env.CUSTOM_DOMAINS || '')\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n);\n\n// basename 优先级:\n// 1. X-App-Basename header(DomainGate 注入,正式上线后的主要来源)\n// 2. host 匹配 CUSTOM_DOMAINS env(demo / 自部署场景,启动时通过 env 列出自定义域名 → basename=/)\n// 3. /app/${appId}(默认域名场景的兜底,跟现状一致)\n//\n// 注意:req.hostname 在 trust proxy 场景下从 X-Forwarded-Host 解析。\n// demo 期 a.com 直接到应用 server(无 proxy),行为可预期。\n// 正式接 DomainGate 后走 header 兜底(第 1 层),env 自动失效,无需再依赖 req.hostname。\nfunction resolveBasename(req: Request, appId?: string): string {\n const headerValue = req.headers['x-app-basename'];\n if (typeof headerValue === 'string' && headerValue) return headerValue;\n\n if (CUSTOM_DOMAINS.size > 0 && CUSTOM_DOMAINS.has(req.hostname)) {\n return '/';\n }\n\n // appId 缺失时不构造伪 basename(避免 \"/app/undefined\"),降级到根路径\n return appId ? `/app/${appId}` : '/';\n}\n\nexport function createViewContextMiddleware() {\n return async function viewContextMiddleware(req: Request, res: Response, next: NextFunction) {\n const { userId, tenantId, appId, loginUrl, userType } = req.userContext;\n const csrfToken = req.csrfToken;\n const appInfo = await getAppInfo(appId);\n const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);\n const basename = resolveBasename(req, appId);\n\n req.__platform_data__ = {\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n tenantId,\n environment,\n basename,\n };\n\n res.locals = {\n ...(res.locals ?? {}),\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n tenantId: tenantId ?? '',\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n environment,\n };\n\n next();\n };\n}\n"],"mappings":"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","names":["express","cookieParser","createDatabase","createRlsMiddleware","SUDA_WEB_USER_HEADER_KEY","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","JSON","parse","err","console","error","userContextMiddleware","_res","next","webUser","userContext","userId","user_id","tenantId","tenant_id","appId","app_id","loginUrl","login_url","userType","user_type","env","userName","user_name","zh_cn","userNameEn","en_us","userNameI18n","isSystemAccount","is_system_account","roles","crypto","pathToRegex","pattern","normalized","replace","escaped","RegExp","resolveOptions","options","cookieKey","cookieMaxAge","cookiePath","exclude","map","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","createCsrfTokenMiddleware","resolved","csrfTokenMiddleware","req","res","next","pathname","path","some","re","test","originToken","cookies","toLowerCase","csrfToken","token","cookie","maxAge","httpOnly","secure","sameSite","partitioned","resolveOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","createCsrfValidationMiddleware","resolved","csrfValidationMiddleware","req","next","cookieCsrfToken","cookies","toLowerCase","headerCsrfToken","headers","HttpClient","_client","createPlatformClient","HttpClient","timeout","platform","enabled","getPlatformClient","Error","safeStringify","obj","json","JSON","stringify","safeEscape","safeEscape","s","replace","c","charCodeAt","getAppInfo","appId","console","warn","client","getPlatformClient","resp","get","status","Error","data","json","status_code","app_info","err","error","mapToWindowEnvironment","input","value","trim","toLowerCase","CUSTOM_DOMAINS","Set","process","env","split","map","d","filter","Boolean","resolveBasename","req","headerValue","headers","size","has","hostname","createViewContextMiddleware","viewContextMiddleware","res","next","userId","tenantId","loginUrl","userType","userContext","csrfToken","appInfo","environment","FORCE_FRAMEWORK_ENVIRONMENT","basename","__platform_data__","appName","safeEscape","app_name","appAvatar","app_avatar","appDescription","app_description","locals","db","setup","app","config","bodyLimit","process","env","BODY_SIZE_LIMIT","use","express","json","limit","urlencoded","extended","cookieParser","userContextMiddleware","connectionString","database","FORCE_DB_CONNECT_URL","close","createDatabase","createRlsMiddleware","csrfEnabled","csrf","enabled","csrfRoutes","routes","createCsrfTokenMiddleware","cookieKey","cookieMaxAge","cookiePath","exclude","createCsrfValidationMiddleware","headerKey","createPlatformClient","createViewContextMiddleware"]}
|
|
1
|
+
{"version":3,"sources":["../src/setup.ts","../src/middlewares/user-context.ts","../src/middlewares/csrf-token.ts","../src/middlewares/csrf-validation.ts","../src/utils/platform-client.ts","../src/utils/safe-escape.ts","../src/middlewares/view-context.ts"],"sourcesContent":["/* eslint-disable */\nimport express from 'express';\nimport cookieParser from 'cookie-parser';\nimport { createDatabase, createRlsMiddleware } from '@lark-apaas/express-datapaas';\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\n\nimport type { SetupConfig } from './types/config';\nimport { userContextMiddleware } from './middlewares/user-context';\nimport { createCsrfTokenMiddleware } from './middlewares/csrf-token';\nimport { createCsrfValidationMiddleware } from './middlewares/csrf-validation';\nimport { createViewContextMiddleware } from './middlewares/view-context';\nimport { createPlatformClient } from './utils/platform-client';\n\n// eslint-disable-next-line import/no-mutable-exports\nexport let db: PostgresJsDatabase;\n\nexport function setup(app: express.Application, config?: SetupConfig): { close: () => Promise<void> } {\n const bodyLimit = config?.bodyLimit ?? process.env.BODY_SIZE_LIMIT ?? '1mb';\n\n // 1. Body parsers\n app.use(express.json({ limit: bodyLimit }));\n app.use(express.urlencoded({ limit: bodyLimit, extended: true }));\n\n // 2. Cookie parser\n app.use(cookieParser());\n\n // 3. UserContext (all routes)\n app.use(userContextMiddleware);\n\n // 4. Database + RLS\n const connectionString = config?.database?.connectionString ?? process.env.FORCE_DB_CONNECT_URL ?? '';\n const { db: database, close } = createDatabase({\n connectionString,\n ...config?.database,\n });\n db = database;\n app.use(createRlsMiddleware());\n\n // 5. CSRF\n const csrfEnabled = config?.csrf?.enabled ?? true;\n if (csrfEnabled) {\n const csrfRoutes = config?.csrf?.routes ?? ['/api/{*path}'];\n\n // 页面路由生成 token(排除 API 路由),为后续 API 请求准备 cookie\n app.use(createCsrfTokenMiddleware({\n cookieKey: config?.csrf?.cookieKey,\n cookieMaxAge: config?.csrf?.cookieMaxAge,\n cookiePath: config?.csrf?.cookiePath,\n exclude: csrfRoutes,\n }));\n\n // API 路由验证 token(cookie vs header)\n app.use(csrfRoutes, createCsrfValidationMiddleware({\n headerKey: config?.csrf?.headerKey,\n cookieKey: config?.csrf?.cookieKey,\n }));\n }\n\n // 6. Platform client + ViewContext\n createPlatformClient();\n app.use(createViewContextMiddleware());\n\n return { close };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nconst SUDA_WEB_USER_HEADER_KEY = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n login_url: string;\n user_type: string;\n env: 'preview' | 'runtime';\n user_name: Record<string, string>;\n is_system_account?: boolean;\n roles?: string[];\n}\n\nfunction getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[SUDA_WEB_USER_HEADER_KEY] as string | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n return JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n}\n\nexport function userContextMiddleware(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n loginUrl: webUser?.login_url ?? '',\n userType: webUser?.user_type ?? '',\n env: webUser?.env ?? 'runtime',\n userName: webUser?.user_name?.zh_cn ?? '',\n userNameEn: webUser?.user_name?.en_us ?? '',\n userNameI18n: webUser?.user_name ?? {},\n isSystemAccount: webUser?.is_system_account ?? false,\n roles: webUser?.roles,\n };\n next();\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport crypto from 'crypto';\n\nexport interface CsrfTokenOptions {\n cookieKey?: string;\n cookieMaxAge?: number;\n cookiePath?: string;\n /** 排除的路由模式,匹配到则跳过 token 生成 */\n exclude?: string[];\n}\n\ninterface ResolvedCsrfTokenOptions {\n cookieKey: string;\n cookieMaxAge: number;\n cookiePath: string;\n exclude: RegExp[];\n}\n\nfunction pathToRegex(pattern: string): RegExp {\n // 将路径模式转为正则:/api/{*path} → /^\\/api\\/.*$/, /api/* → /^\\/api\\/.*$/\n const normalized = pattern.replace(/\\{[*][\\w]*\\}/g, '*');\n const escaped = normalized\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n return new RegExp(`^${escaped}$`);\n}\n\nfunction resolveOptions(options: CsrfTokenOptions): ResolvedCsrfTokenOptions {\n return {\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 * 60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n exclude: (options.exclude ?? []).map(pathToRegex),\n };\n}\n\nfunction genToken(): string {\n const ts = Math.floor(Date.now() / 1000);\n const randInt64 = BigInt('0x' + crypto.randomBytes(8).toString('hex')).toString();\n const s = `${randInt64}.${ts}`;\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n return `${sha1.digest('hex')}-${ts}`;\n}\n\nexport function createCsrfTokenMiddleware(options?: CsrfTokenOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {\n // 匹配排除路由则跳过\n const pathname = req.path;\n if (resolved.exclude.some(re => re.test(pathname))) {\n return next();\n }\n\n const { cookieKey, cookieMaxAge, cookiePath } = resolved;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n\n if (originToken) {\n req.csrfToken = originToken;\n } else {\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: false,\n secure: true,\n sameSite: 'none',\n partitioned: true,\n });\n }\n next();\n };\n}\n","import type { Request, Response, NextFunction } from 'express';\n\nexport interface CsrfValidationOptions {\n headerKey?: string;\n cookieKey?: string;\n}\n\ninterface ResolvedCsrfValidationOptions {\n headerKey: string;\n cookieKey: string;\n}\n\nfunction resolveOptions(options: CsrfValidationOptions): ResolvedCsrfValidationOptions {\n return {\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nfunction sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n\nexport function createCsrfValidationMiddleware(options?: CsrfValidationOptions) {\n const resolved = resolveOptions(options ?? {});\n\n return function csrfValidationMiddleware(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = resolved;\n\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n\n next();\n };\n}\n","import { HttpClient } from '@lark-apaas/http-client';\n\nlet _client: HttpClient | null = null;\n\nexport function createPlatformClient(): HttpClient {\n if (_client) return _client;\n _client = new HttpClient({\n timeout: 5000,\n platform: { enabled: true },\n });\n return _client;\n}\n\nexport function getPlatformClient(): HttpClient {\n if (!_client) {\n throw new Error('Platform client not initialized. Call setup() first.');\n }\n return _client;\n}\n","/**\n * 安全转义的 Json Stringify,模版渲染的 JSON 对象需要调用 safeStringify,而不是用原生的 JSON.stringify。\n * 抄 gson 的实现: https://github.com/google/gson/blob/49ddab9eeb6cbac686711deca6001d40e8d8500d/gson/src/main/java/com/google/gson/stream/JsonWriter.java\n * 将 json 的敏感字符转换为 unicode, 避免 xss\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeStringify(obj: any) {\n const json = JSON.stringify(obj);\n return safeEscape(json);\n}\n\nenum EscapedChar {\n LT = 0x3c, // <\n GT = 0x3e, // >\n AMP = 0x26, // &\n APO = 0x27, // '\n QUOT = 0x22, // \"\n EQUAL = 0x3d, // =\n LF = 0x0a, // \\n\n CR = 0x0d, // \\r\n LS = 0x2028, // 行分隔符\n PS = 0x2029, // 段落分隔符\n}\n\nexport function safeEscape(s: string) {\n return s.replace(/[<>&='\"\\n\\r\\u2028\\u2029]/g, function (c) {\n switch (c.charCodeAt(0)) {\n case EscapedChar.LT:\n return '\\\\u003c';\n case EscapedChar.GT:\n return '\\\\u003e';\n case EscapedChar.AMP:\n return '\\\\u0026';\n case EscapedChar.EQUAL:\n return '\\\\u003d';\n case EscapedChar.APO:\n return '\\\\u0027';\n case EscapedChar.QUOT:\n return '\\\\u0022';\n case EscapedChar.LF:\n return '\\\\u000a';\n case EscapedChar.CR:\n return '\\\\u000d';\n case EscapedChar.LS:\n return '\\\\u2028';\n case EscapedChar.PS:\n return '\\\\u2029';\n default:\n return c;\n }\n });\n}\n","import type { Request, Response, NextFunction } from 'express';\nimport { getPlatformClient } from '../utils/platform-client';\nimport { safeEscape } from '../utils/safe-escape';\n\ninterface GetAppInfoResponse {\n status_code: string;\n data: {\n status_code: number;\n app_info: {\n app_name: string;\n app_description: string;\n app_avatar: string;\n };\n app_runtime_extra: {\n url: string;\n bucket: string;\n };\n };\n}\n\nasync function getAppInfo(appId?: string) {\n if (!appId) {\n console.warn('appId is empty, skip get app info');\n return null;\n }\n try {\n const client = getPlatformClient();\n const resp = await client.get(`/b/${appId}/get_published_v2`);\n if (resp.status !== 200) {\n throw new Error(`Failed to get app info, status: ${resp.status}`);\n }\n const data = (await resp.json()) as GetAppInfoResponse;\n if (data.status_code !== '0') {\n throw new Error(`Failed to get app info, status_code: ${data.status_code}`);\n }\n return data.data.app_info ?? {};\n } catch (err) {\n console.error('Failed to get app info', err);\n return null;\n }\n}\n\nfunction mapToWindowEnvironment(input?: string): string {\n const value = (input || '').trim().toLowerCase();\n if (value === 'boe') return 'staging';\n if (value === 'pre') return 'gray';\n return 'online';\n}\n\nexport function createViewContextMiddleware() {\n return async function viewContextMiddleware(req: Request, res: Response, next: NextFunction) {\n const { userId, tenantId, appId, loginUrl, userType } = req.userContext;\n const csrfToken = req.csrfToken;\n const appInfo = await getAppInfo(appId);\n const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);\n\n req.__platform_data__ = {\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n tenantId,\n environment,\n };\n\n res.locals = {\n ...(res.locals ?? {}),\n csrfToken: csrfToken ?? '',\n userId: userId ?? '',\n tenantId: tenantId ?? '',\n loginUrl: loginUrl ?? '',\n userType: userType ?? '',\n appId: appId ?? '',\n appName: safeEscape(appInfo?.app_name ?? ''),\n appAvatar: appInfo?.app_avatar ?? '',\n appDescription: safeEscape(appInfo?.app_description ?? ''),\n environment,\n };\n\n next();\n };\n}\n"],"mappings":";;;;AACA,OAAOA,aAAa;AACpB,OAAOC,kBAAkB;AACzB,SAASC,gBAAgBC,2BAA2B;;;ACDpD,IAAMC,2BAA2B;AAcjC,SAASC,qBAAqBC,KAAY;AACxC,QAAMC,qBAAqBD,IAAIE,QAAQJ,wBAAAA;AACvC,MAAI,CAACG,oBAAoB;AACvB,WAAO;EACT;AACA,MAAI;AACF,UAAME,qBAAqBC,mBAAmBH,kBAAAA;AAC9C,WAAOI,KAAKC,MAAMH,kBAAAA;EACpB,SAASI,KAAK;AACZC,YAAQC,MAAM,iDAAiDF,GAAAA;AAC/D,WAAO;EACT;AACF;AAZSR;AAcF,SAASW,sBAAsBV,KAAcW,MAAgBC,MAAkB;AACpF,QAAMC,UAAUd,qBAAqBC,GAAAA;AACrCA,MAAIc,cAAc;IAChBC,QAAQF,SAASG;IACjBC,UAAUJ,SAASK;IACnBC,OAAON,SAASO,UAAU;IAC1BC,UAAUR,SAASS,aAAa;IAChCC,UAAUV,SAASW,aAAa;IAChCC,KAAKZ,SAASY,OAAO;IACrBC,UAAUb,SAASc,WAAWC,SAAS;IACvCC,YAAYhB,SAASc,WAAWG,SAAS;IACzCC,cAAclB,SAASc,aAAa,CAAC;IACrCK,iBAAiBnB,SAASoB,qBAAqB;IAC/CC,OAAOrB,SAASqB;EAClB;AACAtB,OAAAA;AACF;AAhBgBF;;;AC7BhB,OAAOyB,YAAY;AAiBnB,SAASC,YAAYC,SAAe;AAElC,QAAMC,aAAaD,QAAQE,QAAQ,iBAAiB,GAAA;AACpD,QAAMC,UAAUF,WACbC,QAAQ,sBAAsB,MAAA,EAC9BA,QAAQ,OAAO,IAAA;AAClB,SAAO,IAAIE,OAAO,IAAID,OAAAA,GAAU;AAClC;AAPSJ;AAST,SAASM,eAAeC,SAAyB;AAC/C,SAAO;IACLC,WAAWD,QAAQC,aAAa;IAChCC,cAAcF,QAAQE,gBAAgB,MAAO,KAAK,KAAK,KAAK;IAC5DC,YAAYH,QAAQG,cAAc;IAClCC,UAAUJ,QAAQI,WAAW,CAAA,GAAIC,IAAIZ,WAAAA;EACvC;AACF;AAPSM;AAST,SAASO,WAAAA;AACP,QAAMC,KAAKC,KAAKC,MAAMC,KAAKC,IAAG,IAAK,GAAA;AACnC,QAAMC,YAAYC,OAAO,OAAOC,OAAOC,YAAY,CAAA,EAAGC,SAAS,KAAA,CAAA,EAAQA,SAAQ;AAC/E,QAAMC,IAAI,GAAGL,SAAAA,IAAaL,EAAAA;AAC1B,QAAMW,OAAOJ,OAAOK,WAAW,MAAA;AAC/BD,OAAKE,OAAOH,CAAAA;AACZ,SAAO,GAAGC,KAAKG,OAAO,KAAA,CAAA,IAAUd,EAAAA;AAClC;AAPSD;AASF,SAASgB,0BAA0BtB,SAA0B;AAClE,QAAMuB,WAAWxB,eAAeC,WAAW,CAAC,CAAA;AAE5C,SAAO,gCAASwB,oBAAoBC,KAAcC,KAAeC,MAAkB;AAEjF,UAAMC,WAAWH,IAAII;AACrB,QAAIN,SAASnB,QAAQ0B,KAAKC,CAAAA,OAAMA,GAAGC,KAAKJ,QAAAA,CAAAA,GAAY;AAClD,aAAOD,KAAAA;IACT;AAEA,UAAM,EAAE1B,WAAWC,cAAcC,WAAU,IAAKoB;AAChD,UAAMU,cAAcR,IAAIS,QAAQjC,UAAUkC,YAAW,CAAA;AAErD,QAAIF,aAAa;AACfR,UAAIW,YAAYH;IAClB,OAAO;AACL,YAAMI,QAAQ/B,SAAAA;AACdmB,UAAIW,YAAYC;AAChBX,UAAIY,OAAOrC,WAAWoC,OAAO;QAC3BE,QAAQrC;QACR2B,MAAM1B;QACNqC,UAAU;QACVC,QAAQ;QACRC,UAAU;QACVC,aAAa;MACf,CAAA;IACF;AACAhB,SAAAA;EACF,GAzBO;AA0BT;AA7BgBL;;;ACjChB,SAASsB,gBAAeC,SAA8B;AACpD,SAAO;IACLC,WAAWD,QAAQC,aAAa;IAChCC,WAAWF,QAAQE,aAAa;EAClC;AACF;AALSH,OAAAA,iBAAAA;AAOT,SAASI,cAAcC,KAAeC,SAAe;AACnDD,MAAIE,OAAO,GAAA,EAAKC,KAAK,kBAAaF,OAAAA,EAAS;AAC7C;AAFSF;AAIF,SAASK,+BAA+BR,SAA+B;AAC5E,QAAMS,WAAWV,gBAAeC,WAAW,CAAC,CAAA;AAE5C,SAAO,gCAASU,yBAAyBC,KAAcP,KAAeQ,MAAkB;AACtF,UAAM,EAAEX,WAAWC,UAAS,IAAKO;AAEjC,UAAMI,kBAAkBF,IAAIG,QAAQZ,UAAUa,YAAW,CAAA;AACzD,QAAI,CAACF,iBAAiB;AACpBV,oBAAcC,KAAK,iCAAA;AACnB;IACF;AAEA,UAAMY,kBAAkBL,IAAIM,QAAQhB,UAAUc,YAAW,CAAA;AACzD,QAAI,CAACC,iBAAiB;AACpBb,oBAAcC,KAAK,iCAAA;AACnB;IACF;AAEA,QAAIS,oBAAoBG,iBAAiB;AACvCb,oBAAcC,KAAK,uBAAA;AACnB;IACF;AAEAQ,SAAAA;EACF,GArBO;AAsBT;AAzBgBJ;;;ACvBhB,SAASU,kBAAkB;AAE3B,IAAIC,UAA6B;AAE1B,SAASC,uBAAAA;AACd,MAAID,QAAS,QAAOA;AACpBA,YAAU,IAAIE,WAAW;IACvBC,SAAS;IACTC,UAAU;MAAEC,SAAS;IAAK;EAC5B,CAAA;AACA,SAAOL;AACT;AAPgBC;AAST,SAASK,oBAAAA;AACd,MAAI,CAACN,SAAS;AACZ,UAAM,IAAIO,MAAM,sDAAA;EAClB;AACA,SAAOP;AACT;AALgBM;;;ACPT,SAASE,cAAcC,KAAQ;AACpC,QAAMC,OAAOC,KAAKC,UAAUH,GAAAA;AAC5B,SAAOI,WAAWH,IAAAA;AACpB;AAHgBF;AAkBT,SAASM,WAAWC,GAAS;AAClC,SAAOA,EAAEC,QAAQ,6BAA6B,SAAUC,GAAC;AACvD,YAAQA,EAAEC,WAAW,CAAA,GAAA;MACnB,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT,KAAA;AACE,eAAO;MACT;AACE,eAAOD;IACX;EACF,CAAA;AACF;AA3BgBH;;;ACJhB,eAAeK,WAAWC,OAAc;AACtC,MAAI,CAACA,OAAO;AACVC,YAAQC,KAAK,mCAAA;AACb,WAAO;EACT;AACA,MAAI;AACF,UAAMC,SAASC,kBAAAA;AACf,UAAMC,OAAO,MAAMF,OAAOG,IAAI,MAAMN,KAAAA,mBAAwB;AAC5D,QAAIK,KAAKE,WAAW,KAAK;AACvB,YAAM,IAAIC,MAAM,mCAAmCH,KAAKE,MAAM,EAAE;IAClE;AACA,UAAME,OAAQ,MAAMJ,KAAKK,KAAI;AAC7B,QAAID,KAAKE,gBAAgB,KAAK;AAC5B,YAAM,IAAIH,MAAM,wCAAwCC,KAAKE,WAAW,EAAE;IAC5E;AACA,WAAOF,KAAKA,KAAKG,YAAY,CAAC;EAChC,SAASC,KAAK;AACZZ,YAAQa,MAAM,0BAA0BD,GAAAA;AACxC,WAAO;EACT;AACF;AApBed;AAsBf,SAASgB,uBAAuBC,OAAc;AAC5C,QAAMC,SAASD,SAAS,IAAIE,KAAI,EAAGC,YAAW;AAC9C,MAAIF,UAAU,MAAO,QAAO;AAC5B,MAAIA,UAAU,MAAO,QAAO;AAC5B,SAAO;AACT;AALSF;AAOF,SAASK,8BAAAA;AACd,SAAO,sCAAeC,sBAAsBC,KAAcC,KAAeC,MAAkB;AACzF,UAAM,EAAEC,QAAQC,UAAU1B,OAAO2B,UAAUC,SAAQ,IAAKN,IAAIO;AAC5D,UAAMC,YAAYR,IAAIQ;AACtB,UAAMC,UAAU,MAAMhC,WAAWC,KAAAA;AACjC,UAAMgC,cAAcjB,uBAAuBkB,QAAQC,IAAIC,2BAA2B;AAElFb,QAAIc,oBAAoB;MACtBN,WAAWA,aAAa;MACxBL,QAAQA,UAAU;MAClBzB,OAAOA,SAAS;MAChBqC,SAASC,WAAWP,SAASQ,YAAY,EAAA;MACzCC,WAAWT,SAASU,cAAc;MAClCC,gBAAgBJ,WAAWP,SAASY,mBAAmB,EAAA;MACvDhB,UAAUA,YAAY;MACtBC,UAAUA,YAAY;MACtBF;MACAM;IACF;AAEAT,QAAIqB,SAAS;MACX,GAAIrB,IAAIqB,UAAU,CAAC;MACnBd,WAAWA,aAAa;MACxBL,QAAQA,UAAU;MAClBC,UAAUA,YAAY;MACtBC,UAAUA,YAAY;MACtBC,UAAUA,YAAY;MACtB5B,OAAOA,SAAS;MAChBqC,SAASC,WAAWP,SAASQ,YAAY,EAAA;MACzCC,WAAWT,SAASU,cAAc;MAClCC,gBAAgBJ,WAAWP,SAASY,mBAAmB,EAAA;MACvDX;IACF;AAEAR,SAAAA;EACF,GAlCO;AAmCT;AApCgBJ;;;ANnCT,IAAIyB;AAEJ,SAASC,MAAMC,KAA0BC,QAAoB;AAClE,QAAMC,YAAYD,QAAQC,aAAaC,QAAQC,IAAIC,mBAAmB;AAGtEL,MAAIM,IAAIC,QAAQC,KAAK;IAAEC,OAAOP;EAAU,CAAA,CAAA;AACxCF,MAAIM,IAAIC,QAAQG,WAAW;IAAED,OAAOP;IAAWS,UAAU;EAAK,CAAA,CAAA;AAG9DX,MAAIM,IAAIM,aAAAA,CAAAA;AAGRZ,MAAIM,IAAIO,qBAAAA;AAGR,QAAMC,mBAAmBb,QAAQc,UAAUD,oBAAoBX,QAAQC,IAAIY,wBAAwB;AACnG,QAAM,EAAElB,IAAIiB,UAAUE,MAAK,IAAKC,eAAe;IAC7CJ;IACA,GAAGb,QAAQc;EACb,CAAA;AACAjB,OAAKiB;AACLf,MAAIM,IAAIa,oBAAAA,CAAAA;AAGR,QAAMC,cAAcnB,QAAQoB,MAAMC,WAAW;AAC7C,MAAIF,aAAa;AACf,UAAMG,aAAatB,QAAQoB,MAAMG,UAAU;MAAC;;AAG5CxB,QAAIM,IAAImB,0BAA0B;MAChCC,WAAWzB,QAAQoB,MAAMK;MACzBC,cAAc1B,QAAQoB,MAAMM;MAC5BC,YAAY3B,QAAQoB,MAAMO;MAC1BC,SAASN;IACX,CAAA,CAAA;AAGAvB,QAAIM,IAAIiB,YAAYO,+BAA+B;MACjDC,WAAW9B,QAAQoB,MAAMU;MACzBL,WAAWzB,QAAQoB,MAAMK;IAC3B,CAAA,CAAA;EACF;AAGAM,uBAAAA;AACAhC,MAAIM,IAAI2B,4BAAAA,CAAAA;AAER,SAAO;IAAEhB;EAAM;AACjB;AA/CgBlB;","names":["express","cookieParser","createDatabase","createRlsMiddleware","SUDA_WEB_USER_HEADER_KEY","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","JSON","parse","err","console","error","userContextMiddleware","_res","next","webUser","userContext","userId","user_id","tenantId","tenant_id","appId","app_id","loginUrl","login_url","userType","user_type","env","userName","user_name","zh_cn","userNameEn","en_us","userNameI18n","isSystemAccount","is_system_account","roles","crypto","pathToRegex","pattern","normalized","replace","escaped","RegExp","resolveOptions","options","cookieKey","cookieMaxAge","cookiePath","exclude","map","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","createCsrfTokenMiddleware","resolved","csrfTokenMiddleware","req","res","next","pathname","path","some","re","test","originToken","cookies","toLowerCase","csrfToken","token","cookie","maxAge","httpOnly","secure","sameSite","partitioned","resolveOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","createCsrfValidationMiddleware","resolved","csrfValidationMiddleware","req","next","cookieCsrfToken","cookies","toLowerCase","headerCsrfToken","headers","HttpClient","_client","createPlatformClient","HttpClient","timeout","platform","enabled","getPlatformClient","Error","safeStringify","obj","json","JSON","stringify","safeEscape","safeEscape","s","replace","c","charCodeAt","getAppInfo","appId","console","warn","client","getPlatformClient","resp","get","status","Error","data","json","status_code","app_info","err","error","mapToWindowEnvironment","input","value","trim","toLowerCase","createViewContextMiddleware","viewContextMiddleware","req","res","next","userId","tenantId","loginUrl","userType","userContext","csrfToken","appInfo","environment","process","env","FORCE_FRAMEWORK_ENVIRONMENT","__platform_data__","appName","safeEscape","app_name","appAvatar","app_avatar","appDescription","app_description","locals","db","setup","app","config","bodyLimit","process","env","BODY_SIZE_LIMIT","use","express","json","limit","urlencoded","extended","cookieParser","userContextMiddleware","connectionString","database","FORCE_DB_CONNECT_URL","close","createDatabase","createRlsMiddleware","csrfEnabled","csrf","enabled","csrfRoutes","routes","createCsrfTokenMiddleware","cookieKey","cookieMaxAge","cookiePath","exclude","createCsrfValidationMiddleware","headerKey","createPlatformClient","createViewContextMiddleware"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/express-core",
|
|
3
|
-
"version": "1.0.2-alpha.
|
|
3
|
+
"version": "1.0.2-alpha.5",
|
|
4
4
|
"description": "Express core framework with platform middleware integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@lark-apaas/express-datapaas": "^1.0.0",
|
|
45
|
-
"@lark-apaas/http-client": "
|
|
45
|
+
"@lark-apaas/http-client": "0.1.6-alpha.2",
|
|
46
46
|
"cookie-parser": "^1.4.7"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|