@514labs/moose-lib 0.6.457 → 0.6.458

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.
@@ -333,6 +333,14 @@ var init_view = __esm({
333
333
  }
334
334
  });
335
335
 
336
+ // src/dmv2/sdk/selectRowPolicy.ts
337
+ var init_selectRowPolicy = __esm({
338
+ "src/dmv2/sdk/selectRowPolicy.ts"() {
339
+ "use strict";
340
+ init_internal();
341
+ }
342
+ });
343
+
336
344
  // src/dmv2/sdk/lifeCycle.ts
337
345
  var init_lifeCycle = __esm({
338
346
  "src/dmv2/sdk/lifeCycle.ts"() {
@@ -350,6 +358,9 @@ var init_webApp = __esm({
350
358
  });
351
359
 
352
360
  // src/dmv2/registry.ts
361
+ function getSelectRowPolicies() {
362
+ return getMooseInternal().selectRowPolicies;
363
+ }
353
364
  var init_registry = __esm({
354
365
  "src/dmv2/registry.ts"() {
355
366
  "use strict";
@@ -372,6 +383,7 @@ var init_dmv2 = __esm({
372
383
  init_materializedView();
373
384
  init_sqlResource();
374
385
  init_view();
386
+ init_selectRowPolicy();
375
387
  init_lifeCycle();
376
388
  init_webApp();
377
389
  init_registry();
@@ -544,6 +556,16 @@ function formatElapsedTime(ms) {
544
556
  const remainingSeconds = seconds % 60;
545
557
  return `${minutes} minutes and ${remainingSeconds.toFixed(2)} seconds`;
546
558
  }
559
+ function buildRowPolicyOptionsFromClaims(config, claims) {
560
+ const clickhouse_settings = /* @__PURE__ */ Object.create(null);
561
+ for (const [settingName, claimName] of Object.entries(config)) {
562
+ const value = claims[claimName];
563
+ if (value !== void 0 && value !== null) {
564
+ clickhouse_settings[settingName] = String(value);
565
+ }
566
+ }
567
+ return { role: MOOSE_RLS_ROLE, clickhouse_settings };
568
+ }
547
569
  async function getTemporalClient(temporalUrl, namespace, clientCert, clientKey, apiKey2) {
548
570
  try {
549
571
  console.info(
@@ -580,7 +602,7 @@ async function getTemporalClient(temporalUrl, namespace, clientCert, clientKey,
580
602
  return void 0;
581
603
  }
582
604
  }
583
- var MooseClient, QueryClient, WorkflowClient;
605
+ var MooseClient, MOOSE_RLS_ROLE, MOOSE_RLS_USER, MOOSE_RLS_SETTING_PREFIX, QueryClient, WorkflowClient;
584
606
  var init_helpers = __esm({
585
607
  "src/consumption-apis/helpers.ts"() {
586
608
  "use strict";
@@ -594,12 +616,17 @@ var init_helpers = __esm({
594
616
  this.workflow = new WorkflowClient(temporalClient);
595
617
  }
596
618
  };
619
+ MOOSE_RLS_ROLE = "moose_rls_role";
620
+ MOOSE_RLS_USER = "moose_rls_user";
621
+ MOOSE_RLS_SETTING_PREFIX = "SQL_moose_rls_";
597
622
  QueryClient = class {
598
623
  client;
599
624
  query_id_prefix;
600
- constructor(client, query_id_prefix) {
625
+ rowPolicyOptions;
626
+ constructor(client, query_id_prefix, rowPolicyOptions) {
601
627
  this.client = client;
602
628
  this.query_id_prefix = query_id_prefix;
629
+ this.rowPolicyOptions = rowPolicyOptions;
603
630
  }
604
631
  async execute(sql3) {
605
632
  const [query, query_params] = toQuery(sql3);
@@ -614,7 +641,11 @@ var init_helpers = __esm({
614
641
  // where response buffering would harm streaming performance and concurrency
615
642
  clickhouse_settings: {
616
643
  asterisk_include_materialized_columns: 1,
617
- asterisk_include_alias_columns: 1
644
+ asterisk_include_alias_columns: 1,
645
+ ...this.rowPolicyOptions?.clickhouse_settings
646
+ },
647
+ ...this.rowPolicyOptions && {
648
+ role: this.rowPolicyOptions.role
618
649
  }
619
650
  });
620
651
  const elapsedMs = performance.now() - start;
@@ -859,13 +890,17 @@ var init_runtime = __esm({
859
890
  const envUseSSL = this._parseBool(
860
891
  this._env("MOOSE_CLICKHOUSE_CONFIG__USE_SSL")
861
892
  );
893
+ const envRlsUser = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_USER");
894
+ const envRlsPassword = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_PASSWORD");
862
895
  return {
863
896
  host: envHost ?? projectConfig.clickhouse_config.host,
864
897
  port: envPort ?? projectConfig.clickhouse_config.host_port.toString(),
865
898
  username: envUser ?? projectConfig.clickhouse_config.user,
866
899
  password: envPassword ?? projectConfig.clickhouse_config.password,
867
900
  database: envDb ?? projectConfig.clickhouse_config.db_name,
868
- useSSL: envUseSSL !== void 0 ? envUseSSL : projectConfig.clickhouse_config.use_ssl || false
901
+ useSSL: envUseSSL !== void 0 ? envUseSSL : projectConfig.clickhouse_config.use_ssl || false,
902
+ rlsUser: envRlsUser ?? projectConfig.clickhouse_config.rls_user ?? void 0,
903
+ rlsPassword: envRlsPassword ?? projectConfig.clickhouse_config.rls_password ?? void 0
869
904
  };
870
905
  }
871
906
  async getStandaloneClickhouseConfig(overrides) {
@@ -880,6 +915,8 @@ var init_runtime = __esm({
880
915
  const envUseSSL = this._parseBool(
881
916
  this._env("MOOSE_CLICKHOUSE_CONFIG__USE_SSL")
882
917
  );
918
+ const envRlsUser = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_USER");
919
+ const envRlsPassword = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_PASSWORD");
883
920
  let projectConfig;
884
921
  try {
885
922
  projectConfig = await readProjectConfig();
@@ -900,7 +937,9 @@ var init_runtime = __esm({
900
937
  username: overrides?.username ?? envUser ?? projectConfig?.clickhouse_config.user ?? defaults.username,
901
938
  password: overrides?.password ?? envPassword ?? projectConfig?.clickhouse_config.password ?? defaults.password,
902
939
  database: overrides?.database ?? envDb ?? projectConfig?.clickhouse_config.db_name ?? defaults.database,
903
- useSSL: overrides?.useSSL ?? envUseSSL ?? projectConfig?.clickhouse_config.use_ssl ?? defaults.useSSL
940
+ useSSL: overrides?.useSSL ?? envUseSSL ?? projectConfig?.clickhouse_config.use_ssl ?? defaults.useSSL,
941
+ rlsUser: envRlsUser ?? projectConfig?.clickhouse_config.rls_user ?? void 0,
942
+ rlsPassword: envRlsPassword ?? projectConfig?.clickhouse_config.rls_password ?? void 0
904
943
  };
905
944
  }
906
945
  async getKafkaConfig() {
@@ -940,54 +979,145 @@ var init_runtime = __esm({
940
979
  var standalone_exports = {};
941
980
  __export(standalone_exports, {
942
981
  getMooseClients: () => getMooseClients,
943
- getMooseUtils: () => getMooseUtils
982
+ getMooseUtils: () => getMooseUtils,
983
+ runWithRequestContext: () => runWithRequestContext
944
984
  });
945
- async function getMooseUtils(req) {
946
- if (req !== void 0) {
985
+ import { AsyncLocalStorage } from "async_hooks";
986
+ function runWithRequestContext(context, fn) {
987
+ return requestContextStorage.run(context, fn);
988
+ }
989
+ function isLegacyRequestArg(arg) {
990
+ if (arg === null || typeof arg !== "object") return false;
991
+ const obj = arg;
992
+ return "method" in obj || "url" in obj || "headers" in obj;
993
+ }
994
+ function getRowPoliciesConfigFromRegistry() {
995
+ const policies = getSelectRowPolicies();
996
+ if (policies.size === 0) return void 0;
997
+ const config = /* @__PURE__ */ Object.create(null);
998
+ for (const policy of policies.values()) {
999
+ config[`${MOOSE_RLS_SETTING_PREFIX}${policy.config.column}`] = policy.config.claim;
1000
+ }
1001
+ return config;
1002
+ }
1003
+ async function getMooseUtils(options) {
1004
+ if (options !== void 0 && isLegacyRequestArg(options)) {
947
1005
  console.warn(
948
- "[DEPRECATED] getMooseUtils(req) no longer requires a request parameter. Use getMooseUtils() instead."
1006
+ "[DEPRECATED] getMooseUtils(req) no longer requires a request parameter. Use getMooseUtils() instead, or getMooseUtils({ rlsContext }) for row policies."
949
1007
  );
1008
+ options = void 0;
950
1009
  }
951
1010
  const runtimeContext = globalThis._mooseRuntimeContext;
952
1011
  if (runtimeContext) {
953
- return {
954
- client: runtimeContext.client,
955
- sql,
956
- jwt: runtimeContext.jwt
957
- };
1012
+ return resolveRuntimeUtils(runtimeContext, options);
958
1013
  }
959
- if (standaloneUtils) {
960
- return standaloneUtils;
1014
+ await ensureStandaloneInit();
1015
+ if (options?.rlsContext) {
1016
+ return createStandaloneRlsUtils(options.rlsContext);
961
1017
  }
962
- if (initPromise) {
963
- return initPromise;
964
- }
965
- initPromise = (async () => {
966
- await Promise.resolve().then(() => (init_runtime(), runtime_exports));
967
- const configRegistry = globalThis._mooseConfigRegistry;
968
- if (!configRegistry) {
1018
+ return standaloneUtils;
1019
+ }
1020
+ function resolveRuntimeUtils(runtimeContext, options) {
1021
+ const reqCtx = requestContextStorage.getStore();
1022
+ const jwt = reqCtx?.jwt ?? runtimeContext.jwt;
1023
+ let rowPolicyOpts;
1024
+ if (options?.rlsContext) {
1025
+ if (!runtimeContext.rowPoliciesConfig) {
969
1026
  throw new Error(
970
- "Moose not initialized. Ensure you're running within a Moose app or have proper configuration set up."
1027
+ "rlsContext was provided but no row policies are configured. Define at least one SelectRowPolicy before using rlsContext."
971
1028
  );
972
1029
  }
973
- const clickhouseConfig = await configRegistry.getStandaloneClickhouseConfig();
974
- const clickhouseClient = getClickhouseClient(
975
- toClientConfig(clickhouseConfig)
1030
+ rowPolicyOpts = buildRowPolicyOptionsFromClaims(
1031
+ runtimeContext.rowPoliciesConfig,
1032
+ options.rlsContext
976
1033
  );
977
- const queryClient = new QueryClient(clickhouseClient, "standalone");
978
- const mooseClient = new MooseClient(queryClient);
979
- standaloneUtils = {
980
- client: mooseClient,
1034
+ } else {
1035
+ rowPolicyOpts = reqCtx?.rowPolicyOpts;
1036
+ }
1037
+ if (rowPolicyOpts) {
1038
+ const rlsClient = runtimeContext.rlsClickhouseClient ?? runtimeContext.clickhouseClient;
1039
+ const scopedQueryClient = new QueryClient(
1040
+ rlsClient,
1041
+ "rls-scoped",
1042
+ rowPolicyOpts
1043
+ );
1044
+ return {
1045
+ client: new MooseClient(scopedQueryClient, runtimeContext.temporalClient),
981
1046
  sql,
982
- jwt: void 0
1047
+ jwt
983
1048
  };
984
- return standaloneUtils;
985
- })();
986
- try {
987
- return await initPromise;
988
- } finally {
989
- initPromise = null;
990
1049
  }
1050
+ return {
1051
+ client: runtimeContext.client,
1052
+ sql,
1053
+ jwt
1054
+ };
1055
+ }
1056
+ async function ensureStandaloneInit() {
1057
+ if (standaloneUtils) return;
1058
+ if (!initPromise) {
1059
+ initPromise = (async () => {
1060
+ await Promise.resolve().then(() => (init_runtime(), runtime_exports));
1061
+ const configRegistry = globalThis._mooseConfigRegistry;
1062
+ if (!configRegistry) {
1063
+ throw new Error(
1064
+ "Moose not initialized. Ensure you're running within a Moose app or have proper configuration set up."
1065
+ );
1066
+ }
1067
+ const clickhouseConfig = await configRegistry.getStandaloneClickhouseConfig();
1068
+ standaloneClickhouseConfig = clickhouseConfig;
1069
+ const clickhouseClient = getClickhouseClient(
1070
+ toClientConfig(clickhouseConfig)
1071
+ );
1072
+ const queryClient = new QueryClient(clickhouseClient, "standalone");
1073
+ const mooseClient = new MooseClient(queryClient);
1074
+ standaloneUtils = {
1075
+ client: mooseClient,
1076
+ sql,
1077
+ jwt: void 0
1078
+ };
1079
+ return standaloneUtils;
1080
+ })();
1081
+ try {
1082
+ await initPromise;
1083
+ } finally {
1084
+ initPromise = null;
1085
+ }
1086
+ } else {
1087
+ await initPromise;
1088
+ }
1089
+ }
1090
+ function createStandaloneRlsUtils(rlsContext) {
1091
+ const rowPoliciesConfig = getRowPoliciesConfigFromRegistry();
1092
+ if (!rowPoliciesConfig) {
1093
+ throw new Error(
1094
+ "rlsContext was provided but no SelectRowPolicy primitives are registered. Define at least one SelectRowPolicy before using rlsContext."
1095
+ );
1096
+ }
1097
+ if (!standaloneRlsClient && standaloneClickhouseConfig) {
1098
+ standaloneRlsClient = getClickhouseClient(
1099
+ toClientConfig({
1100
+ ...standaloneClickhouseConfig,
1101
+ username: standaloneClickhouseConfig.rlsUser ?? MOOSE_RLS_USER,
1102
+ password: standaloneClickhouseConfig.rlsPassword ?? standaloneClickhouseConfig.password
1103
+ })
1104
+ );
1105
+ }
1106
+ const rowPolicyOpts = buildRowPolicyOptionsFromClaims(
1107
+ rowPoliciesConfig,
1108
+ rlsContext
1109
+ );
1110
+ const rlsClient = standaloneRlsClient ?? standaloneUtils.client.query.client;
1111
+ const scopedQueryClient = new QueryClient(
1112
+ rlsClient,
1113
+ "rls-scoped",
1114
+ rowPolicyOpts
1115
+ );
1116
+ return {
1117
+ client: new MooseClient(scopedQueryClient),
1118
+ sql,
1119
+ jwt: void 0
1120
+ };
991
1121
  }
992
1122
  async function getMooseClients(config) {
993
1123
  console.warn(
@@ -1012,15 +1142,19 @@ async function getMooseClients(config) {
1012
1142
  const utils = await getMooseUtils();
1013
1143
  return { client: utils.client };
1014
1144
  }
1015
- var standaloneUtils, initPromise, toClientConfig;
1145
+ var requestContextStorage, standaloneUtils, initPromise, standaloneRlsClient, standaloneClickhouseConfig, toClientConfig;
1016
1146
  var init_standalone = __esm({
1017
1147
  "src/consumption-apis/standalone.ts"() {
1018
1148
  "use strict";
1019
1149
  init_helpers();
1020
1150
  init_commons();
1021
1151
  init_sqlHelpers();
1152
+ init_registry();
1153
+ requestContextStorage = new AsyncLocalStorage();
1022
1154
  standaloneUtils = null;
1023
1155
  initPromise = null;
1156
+ standaloneRlsClient = null;
1157
+ standaloneClickhouseConfig = null;
1024
1158
  toClientConfig = (config) => ({
1025
1159
  ...config,
1026
1160
  useSSL: config.useSSL ? "true" : "false"
@@ -2917,7 +3051,8 @@ function createRegistryFrom(existing) {
2917
3051
  workflows: toTrackingMap(existing?.workflows),
2918
3052
  webApps: toTrackingMap(existing?.webApps),
2919
3053
  materializedViews: toTrackingMap(existing?.materializedViews),
2920
- views: toTrackingMap(existing?.views)
3054
+ views: toTrackingMap(existing?.views),
3055
+ selectRowPolicies: toTrackingMap(existing?.selectRowPolicies)
2921
3056
  };
2922
3057
  }
2923
3058
  function getCachedLineage(registry) {
@@ -3271,7 +3406,11 @@ var init_internal = __esm({
3271
3406
  void 0,
3272
3407
  markRegistryMutated
3273
3408
  ),
3274
- views: new MutationTrackingMap(void 0, markRegistryMutated)
3409
+ views: new MutationTrackingMap(void 0, markRegistryMutated),
3410
+ selectRowPolicies: new MutationTrackingMap(
3411
+ void 0,
3412
+ markRegistryMutated
3413
+ )
3275
3414
  };
3276
3415
  defaultRetentionPeriod = 60 * 60 * 24 * 7;
3277
3416
  toInfraMap = (registry) => {
@@ -3284,6 +3423,7 @@ var init_internal = __esm({
3284
3423
  const webApps = {};
3285
3424
  const materializedViews = {};
3286
3425
  const views = {};
3426
+ const selectRowPolicies = {};
3287
3427
  const lineage = getCachedLineage(registry);
3288
3428
  registry.tables.forEach((table) => {
3289
3429
  const id = table.config.version ? `${table.name}_${table.config.version}` : table.name;
@@ -3531,6 +3671,14 @@ var init_internal = __esm({
3531
3671
  metadata: view.metadata
3532
3672
  };
3533
3673
  });
3674
+ registry.selectRowPolicies.forEach((policy) => {
3675
+ selectRowPolicies[policy.name] = {
3676
+ name: policy.name,
3677
+ tables: policy.tableRefs,
3678
+ column: policy.config.column,
3679
+ claim: policy.config.claim
3680
+ };
3681
+ });
3534
3682
  return {
3535
3683
  topics,
3536
3684
  tables,
@@ -3541,6 +3689,7 @@ var init_internal = __esm({
3541
3689
  webApps,
3542
3690
  materializedViews,
3543
3691
  views,
3692
+ selectRowPolicies,
3544
3693
  unloadedFiles: []
3545
3694
  // Will be populated by dumpMooseInternal
3546
3695
  };
@@ -3584,6 +3733,7 @@ var init_internal = __esm({
3584
3733
  registry.webApps.clear();
3585
3734
  registry.materializedViews.clear();
3586
3735
  registry.views.clear();
3736
+ registry.selectRowPolicies.clear();
3587
3737
  const outDir = getOutDir();
3588
3738
  const compiledDir = path5.isAbsolute(outDir) ? outDir : path5.join(process3.cwd(), outDir);
3589
3739
  Object.keys(__require.cache).forEach((key) => {
@@ -3923,9 +4073,9 @@ init_compiler_config();
3923
4073
 
3924
4074
  // src/utils/structured-logging.ts
3925
4075
  import * as util from "util";
3926
- import { AsyncLocalStorage } from "async_hooks";
4076
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
3927
4077
  function setupStructuredConsole(getContextField, contextFieldName) {
3928
- const contextStorage = new AsyncLocalStorage();
4078
+ const contextStorage = new AsyncLocalStorage2();
3929
4079
  const originalConsole = {
3930
4080
  log: console.log,
3931
4081
  info: console.info,
@@ -4065,7 +4215,61 @@ var apiContextStorage = setupStructuredConsole(
4065
4215
  (ctx) => ctx.apiName,
4066
4216
  "api_name"
4067
4217
  );
4068
- var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig) => {
4218
+ async function authenticateRequest(req, res, publicKey, jwtConfig, enforceAuth, rowPoliciesConfig) {
4219
+ const requireAuth = enforceAuth || !!rowPoliciesConfig;
4220
+ let jwtPayload;
4221
+ if (publicKey && jwtConfig) {
4222
+ const jwt = req.headers.authorization?.split(" ")[1];
4223
+ if (jwt) {
4224
+ try {
4225
+ const { payload } = await jose.jwtVerify(jwt, publicKey, {
4226
+ issuer: jwtConfig.issuer,
4227
+ audience: jwtConfig.audience
4228
+ });
4229
+ jwtPayload = payload;
4230
+ } catch (_error) {
4231
+ console.log("JWT verification failed");
4232
+ if (requireAuth) {
4233
+ res.writeHead(401, { "Content-Type": "application/json" });
4234
+ res.end(JSON.stringify({ error: "Unauthorized" }));
4235
+ return null;
4236
+ }
4237
+ }
4238
+ } else if (requireAuth) {
4239
+ res.writeHead(401, { "Content-Type": "application/json" });
4240
+ res.end(JSON.stringify({ error: "Unauthorized" }));
4241
+ return null;
4242
+ }
4243
+ } else if (requireAuth) {
4244
+ if (rowPoliciesConfig) {
4245
+ res.writeHead(403, { "Content-Type": "application/json" });
4246
+ res.end(
4247
+ JSON.stringify({
4248
+ error: "Forbidden",
4249
+ message: "Row policies require JWT authentication. Configure jwt.secret in moose.config.toml."
4250
+ })
4251
+ );
4252
+ } else {
4253
+ res.writeHead(401, { "Content-Type": "application/json" });
4254
+ res.end(
4255
+ JSON.stringify({
4256
+ error: "Unauthorized",
4257
+ message: "Authentication is enforced but no JWT configuration is available."
4258
+ })
4259
+ );
4260
+ }
4261
+ return null;
4262
+ }
4263
+ let rowPolicyOpts;
4264
+ if (rowPoliciesConfig && jwtPayload) {
4265
+ rowPolicyOpts = buildRowPolicyOptionsFromClaims(
4266
+ rowPoliciesConfig,
4267
+ jwtPayload
4268
+ );
4269
+ }
4270
+ return { jwtPayload, rowPolicyOpts };
4271
+ }
4272
+ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig, rowPoliciesConfig, rlsClickhouseClient) => {
4069
4273
  const sourceDir = getSourceDir();
4070
4274
  const outDir = getOutDir();
4071
4275
  const outRoot = path6.isAbsolute(outDir) ? outDir : path6.join(process.cwd(), outDir);
@@ -4077,37 +4281,19 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4077
4281
  try {
4078
4282
  const url = new URL(req.url || "", "http://localhost");
4079
4283
  const fileName = url.pathname;
4080
- let jwtPayload;
4081
- if (publicKey && jwtConfig) {
4082
- const jwt = req.headers.authorization?.split(" ")[1];
4083
- if (jwt) {
4084
- try {
4085
- const { payload } = await jose.jwtVerify(jwt, publicKey, {
4086
- issuer: jwtConfig.issuer,
4087
- audience: jwtConfig.audience
4088
- });
4089
- jwtPayload = payload;
4090
- } catch (error) {
4091
- console.log("JWT verification failed");
4092
- if (enforceAuth) {
4093
- res.writeHead(401, { "Content-Type": "application/json" });
4094
- res.end(JSON.stringify({ error: "Unauthorized" }));
4095
- httpLogger(req, res, start);
4096
- return;
4097
- }
4098
- }
4099
- } else if (enforceAuth) {
4100
- res.writeHead(401, { "Content-Type": "application/json" });
4101
- res.end(JSON.stringify({ error: "Unauthorized" }));
4102
- httpLogger(req, res, start);
4103
- return;
4104
- }
4105
- } else if (enforceAuth) {
4106
- res.writeHead(401, { "Content-Type": "application/json" });
4107
- res.end(JSON.stringify({ error: "Unauthorized" }));
4284
+ const authResult = await authenticateRequest(
4285
+ req,
4286
+ res,
4287
+ publicKey,
4288
+ jwtConfig,
4289
+ enforceAuth,
4290
+ rowPoliciesConfig
4291
+ );
4292
+ if (!authResult) {
4108
4293
  httpLogger(req, res, start);
4109
4294
  return;
4110
4295
  }
4296
+ const { jwtPayload, rowPolicyOpts } = authResult;
4111
4297
  const pathName = createPath(actualApisDir, fileName);
4112
4298
  const paramsObject = Array.from(url.searchParams.entries()).reduce(
4113
4299
  (obj, [key, value]) => {
@@ -4177,7 +4363,12 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4177
4363
  console.log(`[API] | Executing API: ${matchedApiName}`);
4178
4364
  });
4179
4365
  }
4180
- const queryClient = new QueryClient(clickhouseClient, fileName);
4366
+ const queryClickhouseClient = rowPolicyOpts && rlsClickhouseClient ? rlsClickhouseClient : clickhouseClient;
4367
+ const queryClient = new QueryClient(
4368
+ queryClickhouseClient,
4369
+ fileName,
4370
+ rowPolicyOpts
4371
+ );
4181
4372
  const apiName = matchedApiName;
4182
4373
  const result = await apiContextStorage.run({ apiName }, async () => {
4183
4374
  return await userFuncModule(paramsObject, {
@@ -4233,13 +4424,15 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4233
4424
  }
4234
4425
  };
4235
4426
  };
4236
- var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig) => {
4427
+ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig, rowPoliciesConfig, rlsClickhouseClient) => {
4237
4428
  const apiRequestHandler = await apiHandler(
4238
4429
  publicKey,
4239
4430
  clickhouseClient,
4240
4431
  temporalClient,
4241
4432
  enforceAuth,
4242
- jwtConfig
4433
+ jwtConfig,
4434
+ rowPoliciesConfig,
4435
+ rlsClickhouseClient
4243
4436
  );
4244
4437
  const webApps = await getWebApps2();
4245
4438
  const sortedWebApps = Array.from(webApps.values()).sort((a, b) => {
@@ -4261,30 +4454,22 @@ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enfor
4261
4454
  );
4262
4455
  return;
4263
4456
  }
4264
- let jwtPayload;
4265
- if (publicKey && jwtConfig) {
4266
- const jwt = req.headers.authorization?.split(" ")[1];
4267
- if (jwt) {
4268
- try {
4269
- const { payload } = await jose.jwtVerify(jwt, publicKey, {
4270
- issuer: jwtConfig.issuer,
4271
- audience: jwtConfig.audience
4272
- });
4273
- jwtPayload = payload;
4274
- } catch (error) {
4275
- console.log("JWT verification failed for WebApp route");
4276
- }
4277
- }
4278
- }
4457
+ const authResult = await authenticateRequest(
4458
+ req,
4459
+ res,
4460
+ publicKey,
4461
+ jwtConfig,
4462
+ enforceAuth,
4463
+ rowPoliciesConfig
4464
+ );
4465
+ if (!authResult) return;
4466
+ const { jwtPayload, rowPolicyOpts } = authResult;
4279
4467
  for (const webApp of sortedWebApps) {
4280
4468
  const mountPath = webApp.config.mountPath || "/";
4281
4469
  const normalizedMount = mountPath.endsWith("/") && mountPath !== "/" ? mountPath.slice(0, -1) : mountPath;
4282
4470
  const matches = pathname === normalizedMount || pathname.startsWith(normalizedMount + "/");
4283
4471
  if (matches) {
4284
- if (webApp.config.injectMooseUtils !== false) {
4285
- const { getMooseUtils: getMooseUtils2 } = await Promise.resolve().then(() => (init_standalone(), standalone_exports));
4286
- req.moose = await getMooseUtils2();
4287
- }
4472
+ const { getMooseUtils: getMooseUtils2, runWithRequestContext: runWithRequestContext2 } = await Promise.resolve().then(() => (init_standalone(), standalone_exports));
4288
4473
  let proxiedUrl = req.url;
4289
4474
  if (normalizedMount !== "/") {
4290
4475
  const pathWithoutMount = pathname.substring(normalizedMount.length) || "/";
@@ -4298,7 +4483,15 @@ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enfor
4298
4483
  url: proxiedUrl
4299
4484
  }
4300
4485
  );
4301
- await webApp.handler(modifiedReq, res);
4486
+ await runWithRequestContext2(
4487
+ { rowPolicyOpts, jwt: jwtPayload },
4488
+ async () => {
4489
+ if (webApp.config.injectMooseUtils !== false) {
4490
+ modifiedReq.moose = await getMooseUtils2();
4491
+ }
4492
+ await webApp.handler(modifiedReq, res);
4493
+ }
4494
+ );
4302
4495
  return;
4303
4496
  } catch (error) {
4304
4497
  console.error(`Error in WebApp ${webApp.name}:`, error);
@@ -4349,14 +4542,33 @@ var runApis = async (config) => {
4349
4542
  const clickhouseClient = getClickhouseClient(
4350
4543
  toClientConfig2(config.clickhouseConfig)
4351
4544
  );
4545
+ let rlsClickhouseClient;
4546
+ if (config.rowPoliciesConfig) {
4547
+ rlsClickhouseClient = getClickhouseClient(
4548
+ toClientConfig2({
4549
+ ...config.clickhouseConfig,
4550
+ username: config.clickhouseConfig.rlsUser ?? MOOSE_RLS_USER,
4551
+ password: config.clickhouseConfig.rlsPassword ?? config.clickhouseConfig.password
4552
+ })
4553
+ );
4554
+ }
4352
4555
  let publicKey;
4353
4556
  if (config.jwtConfig?.secret) {
4354
4557
  console.log("Importing JWT public key...");
4355
4558
  publicKey = await jose.importSPKI(config.jwtConfig.secret, "RS256");
4356
4559
  }
4560
+ if (config.rowPoliciesConfig && !publicKey) {
4561
+ console.error(
4562
+ "WARNING: Row policies are configured but no JWT public key is set. All consumption API requests will be rejected with 403. Configure jwt.secret in moose.config.toml to enable authentication."
4563
+ );
4564
+ }
4357
4565
  const runtimeQueryClient = new QueryClient(clickhouseClient, "runtime");
4358
4566
  globalThis._mooseRuntimeContext = {
4359
- client: new MooseClient(runtimeQueryClient, temporalClient)
4567
+ client: new MooseClient(runtimeQueryClient, temporalClient),
4568
+ clickhouseClient,
4569
+ rlsClickhouseClient,
4570
+ temporalClient,
4571
+ rowPoliciesConfig: config.rowPoliciesConfig
4360
4572
  };
4361
4573
  const server = http.createServer(
4362
4574
  await createMainRouter(
@@ -4364,7 +4576,9 @@ var runApis = async (config) => {
4364
4576
  clickhouseClient,
4365
4577
  temporalClient,
4366
4578
  config.enforceAuth,
4367
- config.jwtConfig
4579
+ config.jwtConfig,
4580
+ config.rowPoliciesConfig,
4581
+ rlsClickhouseClient
4368
4582
  )
4369
4583
  );
4370
4584
  const port = config.proxyPort !== void 0 ? config.proxyPort : 4001;
@@ -5383,7 +5597,10 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5383
5597
  "--worker-count <count>",
5384
5598
  "Number of worker processes for the consumption API cluster",
5385
5599
  parseInt
5386
- ).action(
5600
+ ).option(
5601
+ "--row-policies <json>",
5602
+ "JSON map of ClickHouse setting names to JWT claim names for row policy enforcement"
5603
+ ).option("--rls-user <user>", "ClickHouse username for RLS queries").option("--rls-password <password>", "ClickHouse password for RLS queries").action(
5387
5604
  (clickhouseDb, clickhouseHost, clickhousePort, clickhouseUsername, clickhousePassword, options) => {
5388
5605
  runApis({
5389
5606
  clickhouseConfig: {
@@ -5392,7 +5609,9 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5392
5609
  port: clickhousePort,
5393
5610
  username: clickhouseUsername,
5394
5611
  password: clickhousePassword,
5395
- useSSL: options.clickhouseUseSsl
5612
+ useSSL: options.clickhouseUseSsl,
5613
+ rlsUser: options.rlsUser,
5614
+ rlsPassword: options.rlsPassword
5396
5615
  },
5397
5616
  jwtConfig: {
5398
5617
  secret: options.jwtSecret,
@@ -5408,7 +5627,27 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5408
5627
  } : void 0,
5409
5628
  enforceAuth: options.enforceAuth,
5410
5629
  proxyPort: options.proxyPort,
5411
- workerCount: options.workerCount
5630
+ workerCount: options.workerCount,
5631
+ rowPoliciesConfig: options.rowPolicies ? (() => {
5632
+ let parsed;
5633
+ try {
5634
+ parsed = JSON.parse(options.rowPolicies);
5635
+ } catch (e) {
5636
+ console.error(
5637
+ `Failed to parse --row-policies JSON: ${e.message}`
5638
+ );
5639
+ process.exit(1);
5640
+ }
5641
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !Object.values(parsed).every(
5642
+ (v) => typeof v === "string"
5643
+ )) {
5644
+ console.error(
5645
+ `Invalid --row-policies JSON: expected a flat object with string values`
5646
+ );
5647
+ process.exit(1);
5648
+ }
5649
+ return parsed;
5650
+ })() : void 0
5412
5651
  });
5413
5652
  }
5414
5653
  );