@lark-apaas/express-core 1.0.2-alpha.3 → 1.0.2-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -259,12 +259,23 @@ 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");
262
272
  function createViewContextMiddleware() {
263
273
  return /* @__PURE__ */ __name(async function viewContextMiddleware(req, res, next) {
264
274
  const { userId, tenantId, appId, loginUrl, userType } = req.userContext;
265
275
  const csrfToken = req.csrfToken;
266
276
  const appInfo = await getAppInfo(appId);
267
277
  const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);
278
+ const basename = resolveBasename(req, appId);
268
279
  req.__platform_data__ = {
269
280
  csrfToken: csrfToken ?? "",
270
281
  userId: userId ?? "",
@@ -275,7 +286,8 @@ function createViewContextMiddleware() {
275
286
  loginUrl: loginUrl ?? "",
276
287
  userType: userType ?? "",
277
288
  tenantId,
278
- environment
289
+ environment,
290
+ basename
279
291
  };
280
292
  res.locals = {
281
293
  ...res.locals ?? {},
@@ -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\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;ACCA,qBAAoB;AACpB,2BAAyB;AACzB,8BAAoD;;;ACDpD,IAAMA,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,oBAAmB;AAiBnB,SAASyB,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,cAAAA,QAAOC,YAAY,CAAA,EAAGC,SAAS,KAAA,CAAA,EAAQA,SAAQ;AAC/E,QAAMC,IAAI,GAAGL,SAAAA,IAAaL,EAAAA;AAC1B,QAAMW,OAAOJ,cAAAA,QAAOK,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,yBAA2B;AAE3B,IAAIU,UAA6B;AAE1B,SAASC,uBAAAA;AACd,MAAID,QAAS,QAAOA;AACpBA,YAAU,IAAIE,8BAAW;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,eAAAA,QAAQC,KAAK;IAAEC,OAAOP;EAAU,CAAA,CAAA;AACxCF,MAAIM,IAAIC,eAAAA,QAAQG,WAAW;IAAED,OAAOP;IAAWS,UAAU;EAAK,CAAA,CAAA;AAG9DX,MAAIM,QAAIM,qBAAAA,SAAAA,CAAAA;AAGRZ,MAAIM,IAAIO,qBAAAA;AAGR,QAAMC,mBAAmBb,QAAQc,UAAUD,oBAAoBX,QAAQC,IAAIY,wBAAwB;AACnG,QAAM,EAAElB,IAAIiB,UAAUE,MAAK,QAAKC,wCAAe;IAC7CJ;IACA,GAAGb,QAAQc;EACb,CAAA;AACAjB,OAAKiB;AACLf,MAAIM,QAAIa,6CAAAA,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":["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"]}
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"]}
package/dist/index.d.cts CHANGED
@@ -12,6 +12,8 @@ declare global {
12
12
  userId?: string;
13
13
  tenantId?: number;
14
14
  csrfToken?: string;
15
+ /** React Router basename,由 viewContextMiddleware 注入;自定义域名场景下 = / 或 /<alias>,默认域名 = /app/<appId> */
16
+ basename?: string;
15
17
  [key: string]: unknown;
16
18
  };
17
19
  userContext: {
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ declare global {
12
12
  userId?: string;
13
13
  tenantId?: number;
14
14
  csrfToken?: string;
15
+ /** React Router basename,由 viewContextMiddleware 注入;自定义域名场景下 = / 或 /<alias>,默认域名 = /app/<appId> */
16
+ basename?: string;
15
17
  [key: string]: unknown;
16
18
  };
17
19
  userContext: {
package/dist/index.js CHANGED
@@ -218,12 +218,23 @@ 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");
221
231
  function createViewContextMiddleware() {
222
232
  return /* @__PURE__ */ __name(async function viewContextMiddleware(req, res, next) {
223
233
  const { userId, tenantId, appId, loginUrl, userType } = req.userContext;
224
234
  const csrfToken = req.csrfToken;
225
235
  const appInfo = await getAppInfo(appId);
226
236
  const environment = mapToWindowEnvironment(process.env.FORCE_FRAMEWORK_ENVIRONMENT);
237
+ const basename = resolveBasename(req, appId);
227
238
  req.__platform_data__ = {
228
239
  csrfToken: csrfToken ?? "",
229
240
  userId: userId ?? "",
@@ -234,7 +245,8 @@ function createViewContextMiddleware() {
234
245
  loginUrl: loginUrl ?? "",
235
246
  userType: userType ?? "",
236
247
  tenantId,
237
- environment
248
+ environment,
249
+ basename
238
250
  };
239
251
  res.locals = {
240
252
  ...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\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":["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"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/express-core",
3
- "version": "1.0.2-alpha.3",
3
+ "version": "1.0.2-alpha.4",
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": "0.1.6-alpha.1",
45
+ "@lark-apaas/http-client": "^0.1.5",
46
46
  "cookie-parser": "^1.4.7"
47
47
  },
48
48
  "devDependencies": {