@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.
@@ -350,6 +350,14 @@ var init_view = __esm({
350
350
  }
351
351
  });
352
352
 
353
+ // src/dmv2/sdk/selectRowPolicy.ts
354
+ var init_selectRowPolicy = __esm({
355
+ "src/dmv2/sdk/selectRowPolicy.ts"() {
356
+ "use strict";
357
+ init_internal();
358
+ }
359
+ });
360
+
353
361
  // src/dmv2/sdk/lifeCycle.ts
354
362
  var init_lifeCycle = __esm({
355
363
  "src/dmv2/sdk/lifeCycle.ts"() {
@@ -367,6 +375,9 @@ var init_webApp = __esm({
367
375
  });
368
376
 
369
377
  // src/dmv2/registry.ts
378
+ function getSelectRowPolicies() {
379
+ return getMooseInternal().selectRowPolicies;
380
+ }
370
381
  var init_registry = __esm({
371
382
  "src/dmv2/registry.ts"() {
372
383
  "use strict";
@@ -389,6 +400,7 @@ var init_dmv2 = __esm({
389
400
  init_materializedView();
390
401
  init_sqlResource();
391
402
  init_view();
403
+ init_selectRowPolicy();
392
404
  init_lifeCycle();
393
405
  init_webApp();
394
406
  init_registry();
@@ -554,6 +566,16 @@ function formatElapsedTime(ms) {
554
566
  const remainingSeconds = seconds % 60;
555
567
  return `${minutes} minutes and ${remainingSeconds.toFixed(2)} seconds`;
556
568
  }
569
+ function buildRowPolicyOptionsFromClaims(config, claims) {
570
+ const clickhouse_settings = /* @__PURE__ */ Object.create(null);
571
+ for (const [settingName, claimName] of Object.entries(config)) {
572
+ const value = claims[claimName];
573
+ if (value !== void 0 && value !== null) {
574
+ clickhouse_settings[settingName] = String(value);
575
+ }
576
+ }
577
+ return { role: MOOSE_RLS_ROLE, clickhouse_settings };
578
+ }
557
579
  async function getTemporalClient(temporalUrl, namespace, clientCert, clientKey, apiKey2) {
558
580
  try {
559
581
  console.info(
@@ -590,7 +612,7 @@ async function getTemporalClient(temporalUrl, namespace, clientCert, clientKey,
590
612
  return void 0;
591
613
  }
592
614
  }
593
- var import_client2, import_node_crypto3, import_perf_hooks, fs, MooseClient, QueryClient, WorkflowClient;
615
+ var import_client2, import_node_crypto3, import_perf_hooks, fs, MooseClient, MOOSE_RLS_ROLE, MOOSE_RLS_USER, MOOSE_RLS_SETTING_PREFIX, QueryClient, WorkflowClient;
594
616
  var init_helpers = __esm({
595
617
  "src/consumption-apis/helpers.ts"() {
596
618
  "use strict";
@@ -608,12 +630,17 @@ var init_helpers = __esm({
608
630
  this.workflow = new WorkflowClient(temporalClient);
609
631
  }
610
632
  };
633
+ MOOSE_RLS_ROLE = "moose_rls_role";
634
+ MOOSE_RLS_USER = "moose_rls_user";
635
+ MOOSE_RLS_SETTING_PREFIX = "SQL_moose_rls_";
611
636
  QueryClient = class {
612
637
  client;
613
638
  query_id_prefix;
614
- constructor(client, query_id_prefix) {
639
+ rowPolicyOptions;
640
+ constructor(client, query_id_prefix, rowPolicyOptions) {
615
641
  this.client = client;
616
642
  this.query_id_prefix = query_id_prefix;
643
+ this.rowPolicyOptions = rowPolicyOptions;
617
644
  }
618
645
  async execute(sql3) {
619
646
  const [query, query_params] = toQuery(sql3);
@@ -628,7 +655,11 @@ var init_helpers = __esm({
628
655
  // where response buffering would harm streaming performance and concurrency
629
656
  clickhouse_settings: {
630
657
  asterisk_include_materialized_columns: 1,
631
- asterisk_include_alias_columns: 1
658
+ asterisk_include_alias_columns: 1,
659
+ ...this.rowPolicyOptions?.clickhouse_settings
660
+ },
661
+ ...this.rowPolicyOptions && {
662
+ role: this.rowPolicyOptions.role
632
663
  }
633
664
  });
634
665
  const elapsedMs = import_perf_hooks.performance.now() - start;
@@ -874,13 +905,17 @@ var init_runtime = __esm({
874
905
  const envUseSSL = this._parseBool(
875
906
  this._env("MOOSE_CLICKHOUSE_CONFIG__USE_SSL")
876
907
  );
908
+ const envRlsUser = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_USER");
909
+ const envRlsPassword = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_PASSWORD");
877
910
  return {
878
911
  host: envHost ?? projectConfig.clickhouse_config.host,
879
912
  port: envPort ?? projectConfig.clickhouse_config.host_port.toString(),
880
913
  username: envUser ?? projectConfig.clickhouse_config.user,
881
914
  password: envPassword ?? projectConfig.clickhouse_config.password,
882
915
  database: envDb ?? projectConfig.clickhouse_config.db_name,
883
- useSSL: envUseSSL !== void 0 ? envUseSSL : projectConfig.clickhouse_config.use_ssl || false
916
+ useSSL: envUseSSL !== void 0 ? envUseSSL : projectConfig.clickhouse_config.use_ssl || false,
917
+ rlsUser: envRlsUser ?? projectConfig.clickhouse_config.rls_user ?? void 0,
918
+ rlsPassword: envRlsPassword ?? projectConfig.clickhouse_config.rls_password ?? void 0
884
919
  };
885
920
  }
886
921
  async getStandaloneClickhouseConfig(overrides) {
@@ -895,6 +930,8 @@ var init_runtime = __esm({
895
930
  const envUseSSL = this._parseBool(
896
931
  this._env("MOOSE_CLICKHOUSE_CONFIG__USE_SSL")
897
932
  );
933
+ const envRlsUser = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_USER");
934
+ const envRlsPassword = this._env("MOOSE_CLICKHOUSE_CONFIG__RLS_PASSWORD");
898
935
  let projectConfig;
899
936
  try {
900
937
  projectConfig = await readProjectConfig();
@@ -915,7 +952,9 @@ var init_runtime = __esm({
915
952
  username: overrides?.username ?? envUser ?? projectConfig?.clickhouse_config.user ?? defaults.username,
916
953
  password: overrides?.password ?? envPassword ?? projectConfig?.clickhouse_config.password ?? defaults.password,
917
954
  database: overrides?.database ?? envDb ?? projectConfig?.clickhouse_config.db_name ?? defaults.database,
918
- useSSL: overrides?.useSSL ?? envUseSSL ?? projectConfig?.clickhouse_config.use_ssl ?? defaults.useSSL
955
+ useSSL: overrides?.useSSL ?? envUseSSL ?? projectConfig?.clickhouse_config.use_ssl ?? defaults.useSSL,
956
+ rlsUser: envRlsUser ?? projectConfig?.clickhouse_config.rls_user ?? void 0,
957
+ rlsPassword: envRlsPassword ?? projectConfig?.clickhouse_config.rls_password ?? void 0
919
958
  };
920
959
  }
921
960
  async getKafkaConfig() {
@@ -955,54 +994,144 @@ var init_runtime = __esm({
955
994
  var standalone_exports = {};
956
995
  __export(standalone_exports, {
957
996
  getMooseClients: () => getMooseClients,
958
- getMooseUtils: () => getMooseUtils
997
+ getMooseUtils: () => getMooseUtils,
998
+ runWithRequestContext: () => runWithRequestContext
959
999
  });
960
- async function getMooseUtils(req) {
961
- if (req !== void 0) {
1000
+ function runWithRequestContext(context, fn) {
1001
+ return requestContextStorage.run(context, fn);
1002
+ }
1003
+ function isLegacyRequestArg(arg) {
1004
+ if (arg === null || typeof arg !== "object") return false;
1005
+ const obj = arg;
1006
+ return "method" in obj || "url" in obj || "headers" in obj;
1007
+ }
1008
+ function getRowPoliciesConfigFromRegistry() {
1009
+ const policies = getSelectRowPolicies();
1010
+ if (policies.size === 0) return void 0;
1011
+ const config = /* @__PURE__ */ Object.create(null);
1012
+ for (const policy of policies.values()) {
1013
+ config[`${MOOSE_RLS_SETTING_PREFIX}${policy.config.column}`] = policy.config.claim;
1014
+ }
1015
+ return config;
1016
+ }
1017
+ async function getMooseUtils(options) {
1018
+ if (options !== void 0 && isLegacyRequestArg(options)) {
962
1019
  console.warn(
963
- "[DEPRECATED] getMooseUtils(req) no longer requires a request parameter. Use getMooseUtils() instead."
1020
+ "[DEPRECATED] getMooseUtils(req) no longer requires a request parameter. Use getMooseUtils() instead, or getMooseUtils({ rlsContext }) for row policies."
964
1021
  );
1022
+ options = void 0;
965
1023
  }
966
1024
  const runtimeContext = globalThis._mooseRuntimeContext;
967
1025
  if (runtimeContext) {
968
- return {
969
- client: runtimeContext.client,
970
- sql,
971
- jwt: runtimeContext.jwt
972
- };
973
- }
974
- if (standaloneUtils) {
975
- return standaloneUtils;
1026
+ return resolveRuntimeUtils(runtimeContext, options);
976
1027
  }
977
- if (initPromise) {
978
- return initPromise;
1028
+ await ensureStandaloneInit();
1029
+ if (options?.rlsContext) {
1030
+ return createStandaloneRlsUtils(options.rlsContext);
979
1031
  }
980
- initPromise = (async () => {
981
- await Promise.resolve().then(() => (init_runtime(), runtime_exports));
982
- const configRegistry = globalThis._mooseConfigRegistry;
983
- if (!configRegistry) {
1032
+ return standaloneUtils;
1033
+ }
1034
+ function resolveRuntimeUtils(runtimeContext, options) {
1035
+ const reqCtx = requestContextStorage.getStore();
1036
+ const jwt = reqCtx?.jwt ?? runtimeContext.jwt;
1037
+ let rowPolicyOpts;
1038
+ if (options?.rlsContext) {
1039
+ if (!runtimeContext.rowPoliciesConfig) {
984
1040
  throw new Error(
985
- "Moose not initialized. Ensure you're running within a Moose app or have proper configuration set up."
1041
+ "rlsContext was provided but no row policies are configured. Define at least one SelectRowPolicy before using rlsContext."
986
1042
  );
987
1043
  }
988
- const clickhouseConfig = await configRegistry.getStandaloneClickhouseConfig();
989
- const clickhouseClient = getClickhouseClient(
990
- toClientConfig(clickhouseConfig)
1044
+ rowPolicyOpts = buildRowPolicyOptionsFromClaims(
1045
+ runtimeContext.rowPoliciesConfig,
1046
+ options.rlsContext
991
1047
  );
992
- const queryClient = new QueryClient(clickhouseClient, "standalone");
993
- const mooseClient = new MooseClient(queryClient);
994
- standaloneUtils = {
995
- client: mooseClient,
1048
+ } else {
1049
+ rowPolicyOpts = reqCtx?.rowPolicyOpts;
1050
+ }
1051
+ if (rowPolicyOpts) {
1052
+ const rlsClient = runtimeContext.rlsClickhouseClient ?? runtimeContext.clickhouseClient;
1053
+ const scopedQueryClient = new QueryClient(
1054
+ rlsClient,
1055
+ "rls-scoped",
1056
+ rowPolicyOpts
1057
+ );
1058
+ return {
1059
+ client: new MooseClient(scopedQueryClient, runtimeContext.temporalClient),
996
1060
  sql,
997
- jwt: void 0
1061
+ jwt
998
1062
  };
999
- return standaloneUtils;
1000
- })();
1001
- try {
1002
- return await initPromise;
1003
- } finally {
1004
- initPromise = null;
1005
1063
  }
1064
+ return {
1065
+ client: runtimeContext.client,
1066
+ sql,
1067
+ jwt
1068
+ };
1069
+ }
1070
+ async function ensureStandaloneInit() {
1071
+ if (standaloneUtils) return;
1072
+ if (!initPromise) {
1073
+ initPromise = (async () => {
1074
+ await Promise.resolve().then(() => (init_runtime(), runtime_exports));
1075
+ const configRegistry = globalThis._mooseConfigRegistry;
1076
+ if (!configRegistry) {
1077
+ throw new Error(
1078
+ "Moose not initialized. Ensure you're running within a Moose app or have proper configuration set up."
1079
+ );
1080
+ }
1081
+ const clickhouseConfig = await configRegistry.getStandaloneClickhouseConfig();
1082
+ standaloneClickhouseConfig = clickhouseConfig;
1083
+ const clickhouseClient = getClickhouseClient(
1084
+ toClientConfig(clickhouseConfig)
1085
+ );
1086
+ const queryClient = new QueryClient(clickhouseClient, "standalone");
1087
+ const mooseClient = new MooseClient(queryClient);
1088
+ standaloneUtils = {
1089
+ client: mooseClient,
1090
+ sql,
1091
+ jwt: void 0
1092
+ };
1093
+ return standaloneUtils;
1094
+ })();
1095
+ try {
1096
+ await initPromise;
1097
+ } finally {
1098
+ initPromise = null;
1099
+ }
1100
+ } else {
1101
+ await initPromise;
1102
+ }
1103
+ }
1104
+ function createStandaloneRlsUtils(rlsContext) {
1105
+ const rowPoliciesConfig = getRowPoliciesConfigFromRegistry();
1106
+ if (!rowPoliciesConfig) {
1107
+ throw new Error(
1108
+ "rlsContext was provided but no SelectRowPolicy primitives are registered. Define at least one SelectRowPolicy before using rlsContext."
1109
+ );
1110
+ }
1111
+ if (!standaloneRlsClient && standaloneClickhouseConfig) {
1112
+ standaloneRlsClient = getClickhouseClient(
1113
+ toClientConfig({
1114
+ ...standaloneClickhouseConfig,
1115
+ username: standaloneClickhouseConfig.rlsUser ?? MOOSE_RLS_USER,
1116
+ password: standaloneClickhouseConfig.rlsPassword ?? standaloneClickhouseConfig.password
1117
+ })
1118
+ );
1119
+ }
1120
+ const rowPolicyOpts = buildRowPolicyOptionsFromClaims(
1121
+ rowPoliciesConfig,
1122
+ rlsContext
1123
+ );
1124
+ const rlsClient = standaloneRlsClient ?? standaloneUtils.client.query.client;
1125
+ const scopedQueryClient = new QueryClient(
1126
+ rlsClient,
1127
+ "rls-scoped",
1128
+ rowPolicyOpts
1129
+ );
1130
+ return {
1131
+ client: new MooseClient(scopedQueryClient),
1132
+ sql,
1133
+ jwt: void 0
1134
+ };
1006
1135
  }
1007
1136
  async function getMooseClients(config) {
1008
1137
  console.warn(
@@ -1027,15 +1156,20 @@ async function getMooseClients(config) {
1027
1156
  const utils = await getMooseUtils();
1028
1157
  return { client: utils.client };
1029
1158
  }
1030
- var standaloneUtils, initPromise, toClientConfig;
1159
+ var import_node_async_hooks, requestContextStorage, standaloneUtils, initPromise, standaloneRlsClient, standaloneClickhouseConfig, toClientConfig;
1031
1160
  var init_standalone = __esm({
1032
1161
  "src/consumption-apis/standalone.ts"() {
1033
1162
  "use strict";
1034
1163
  init_helpers();
1035
1164
  init_commons();
1036
1165
  init_sqlHelpers();
1166
+ init_registry();
1167
+ import_node_async_hooks = require("async_hooks");
1168
+ requestContextStorage = new import_node_async_hooks.AsyncLocalStorage();
1037
1169
  standaloneUtils = null;
1038
1170
  initPromise = null;
1171
+ standaloneRlsClient = null;
1172
+ standaloneClickhouseConfig = null;
1039
1173
  toClientConfig = (config) => ({
1040
1174
  ...config,
1041
1175
  useSSL: config.useSSL ? "true" : "false"
@@ -2931,7 +3065,8 @@ function createRegistryFrom(existing) {
2931
3065
  workflows: toTrackingMap(existing?.workflows),
2932
3066
  webApps: toTrackingMap(existing?.webApps),
2933
3067
  materializedViews: toTrackingMap(existing?.materializedViews),
2934
- views: toTrackingMap(existing?.views)
3068
+ views: toTrackingMap(existing?.views),
3069
+ selectRowPolicies: toTrackingMap(existing?.selectRowPolicies)
2935
3070
  };
2936
3071
  }
2937
3072
  function getCachedLineage(registry) {
@@ -3287,7 +3422,11 @@ var init_internal = __esm({
3287
3422
  void 0,
3288
3423
  markRegistryMutated
3289
3424
  ),
3290
- views: new MutationTrackingMap(void 0, markRegistryMutated)
3425
+ views: new MutationTrackingMap(void 0, markRegistryMutated),
3426
+ selectRowPolicies: new MutationTrackingMap(
3427
+ void 0,
3428
+ markRegistryMutated
3429
+ )
3291
3430
  };
3292
3431
  defaultRetentionPeriod = 60 * 60 * 24 * 7;
3293
3432
  toInfraMap = (registry) => {
@@ -3300,6 +3439,7 @@ var init_internal = __esm({
3300
3439
  const webApps = {};
3301
3440
  const materializedViews = {};
3302
3441
  const views = {};
3442
+ const selectRowPolicies = {};
3303
3443
  const lineage = getCachedLineage(registry);
3304
3444
  registry.tables.forEach((table) => {
3305
3445
  const id = table.config.version ? `${table.name}_${table.config.version}` : table.name;
@@ -3547,6 +3687,14 @@ var init_internal = __esm({
3547
3687
  metadata: view.metadata
3548
3688
  };
3549
3689
  });
3690
+ registry.selectRowPolicies.forEach((policy) => {
3691
+ selectRowPolicies[policy.name] = {
3692
+ name: policy.name,
3693
+ tables: policy.tableRefs,
3694
+ column: policy.config.column,
3695
+ claim: policy.config.claim
3696
+ };
3697
+ });
3550
3698
  return {
3551
3699
  topics,
3552
3700
  tables,
@@ -3557,6 +3705,7 @@ var init_internal = __esm({
3557
3705
  webApps,
3558
3706
  materializedViews,
3559
3707
  views,
3708
+ selectRowPolicies,
3560
3709
  unloadedFiles: []
3561
3710
  // Will be populated by dumpMooseInternal
3562
3711
  };
@@ -3600,6 +3749,7 @@ var init_internal = __esm({
3600
3749
  registry.webApps.clear();
3601
3750
  registry.materializedViews.clear();
3602
3751
  registry.views.clear();
3752
+ registry.selectRowPolicies.clear();
3603
3753
  const outDir = getOutDir();
3604
3754
  const compiledDir = path5.isAbsolute(outDir) ? outDir : path5.join(import_process.default.cwd(), outDir);
3605
3755
  Object.keys(require.cache).forEach((key) => {
@@ -4081,7 +4231,61 @@ var apiContextStorage = setupStructuredConsole(
4081
4231
  (ctx) => ctx.apiName,
4082
4232
  "api_name"
4083
4233
  );
4084
- var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig) => {
4234
+ async function authenticateRequest(req, res, publicKey, jwtConfig, enforceAuth, rowPoliciesConfig) {
4235
+ const requireAuth = enforceAuth || !!rowPoliciesConfig;
4236
+ let jwtPayload;
4237
+ if (publicKey && jwtConfig) {
4238
+ const jwt = req.headers.authorization?.split(" ")[1];
4239
+ if (jwt) {
4240
+ try {
4241
+ const { payload } = await jose.jwtVerify(jwt, publicKey, {
4242
+ issuer: jwtConfig.issuer,
4243
+ audience: jwtConfig.audience
4244
+ });
4245
+ jwtPayload = payload;
4246
+ } catch (_error) {
4247
+ console.log("JWT verification failed");
4248
+ if (requireAuth) {
4249
+ res.writeHead(401, { "Content-Type": "application/json" });
4250
+ res.end(JSON.stringify({ error: "Unauthorized" }));
4251
+ return null;
4252
+ }
4253
+ }
4254
+ } else if (requireAuth) {
4255
+ res.writeHead(401, { "Content-Type": "application/json" });
4256
+ res.end(JSON.stringify({ error: "Unauthorized" }));
4257
+ return null;
4258
+ }
4259
+ } else if (requireAuth) {
4260
+ if (rowPoliciesConfig) {
4261
+ res.writeHead(403, { "Content-Type": "application/json" });
4262
+ res.end(
4263
+ JSON.stringify({
4264
+ error: "Forbidden",
4265
+ message: "Row policies require JWT authentication. Configure jwt.secret in moose.config.toml."
4266
+ })
4267
+ );
4268
+ } else {
4269
+ res.writeHead(401, { "Content-Type": "application/json" });
4270
+ res.end(
4271
+ JSON.stringify({
4272
+ error: "Unauthorized",
4273
+ message: "Authentication is enforced but no JWT configuration is available."
4274
+ })
4275
+ );
4276
+ }
4277
+ return null;
4278
+ }
4279
+ let rowPolicyOpts;
4280
+ if (rowPoliciesConfig && jwtPayload) {
4281
+ rowPolicyOpts = buildRowPolicyOptionsFromClaims(
4282
+ rowPoliciesConfig,
4283
+ jwtPayload
4284
+ );
4285
+ }
4286
+ return { jwtPayload, rowPolicyOpts };
4287
+ }
4288
+ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig, rowPoliciesConfig, rlsClickhouseClient) => {
4085
4289
  const sourceDir = getSourceDir();
4086
4290
  const outDir = getOutDir();
4087
4291
  const outRoot = path6.isAbsolute(outDir) ? outDir : path6.join(process.cwd(), outDir);
@@ -4093,37 +4297,19 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4093
4297
  try {
4094
4298
  const url = new URL(req.url || "", "http://localhost");
4095
4299
  const fileName = url.pathname;
4096
- let jwtPayload;
4097
- if (publicKey && jwtConfig) {
4098
- const jwt = req.headers.authorization?.split(" ")[1];
4099
- if (jwt) {
4100
- try {
4101
- const { payload } = await jose.jwtVerify(jwt, publicKey, {
4102
- issuer: jwtConfig.issuer,
4103
- audience: jwtConfig.audience
4104
- });
4105
- jwtPayload = payload;
4106
- } catch (error) {
4107
- console.log("JWT verification failed");
4108
- if (enforceAuth) {
4109
- res.writeHead(401, { "Content-Type": "application/json" });
4110
- res.end(JSON.stringify({ error: "Unauthorized" }));
4111
- httpLogger(req, res, start);
4112
- return;
4113
- }
4114
- }
4115
- } else if (enforceAuth) {
4116
- res.writeHead(401, { "Content-Type": "application/json" });
4117
- res.end(JSON.stringify({ error: "Unauthorized" }));
4118
- httpLogger(req, res, start);
4119
- return;
4120
- }
4121
- } else if (enforceAuth) {
4122
- res.writeHead(401, { "Content-Type": "application/json" });
4123
- res.end(JSON.stringify({ error: "Unauthorized" }));
4300
+ const authResult = await authenticateRequest(
4301
+ req,
4302
+ res,
4303
+ publicKey,
4304
+ jwtConfig,
4305
+ enforceAuth,
4306
+ rowPoliciesConfig
4307
+ );
4308
+ if (!authResult) {
4124
4309
  httpLogger(req, res, start);
4125
4310
  return;
4126
4311
  }
4312
+ const { jwtPayload, rowPolicyOpts } = authResult;
4127
4313
  const pathName = createPath(actualApisDir, fileName);
4128
4314
  const paramsObject = Array.from(url.searchParams.entries()).reduce(
4129
4315
  (obj, [key, value]) => {
@@ -4193,7 +4379,12 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4193
4379
  console.log(`[API] | Executing API: ${matchedApiName}`);
4194
4380
  });
4195
4381
  }
4196
- const queryClient = new QueryClient(clickhouseClient, fileName);
4382
+ const queryClickhouseClient = rowPolicyOpts && rlsClickhouseClient ? rlsClickhouseClient : clickhouseClient;
4383
+ const queryClient = new QueryClient(
4384
+ queryClickhouseClient,
4385
+ fileName,
4386
+ rowPolicyOpts
4387
+ );
4197
4388
  const apiName = matchedApiName;
4198
4389
  const result = await apiContextStorage.run({ apiName }, async () => {
4199
4390
  return await userFuncModule(paramsObject, {
@@ -4249,13 +4440,15 @@ var apiHandler = async (publicKey, clickhouseClient, temporalClient, enforceAuth
4249
4440
  }
4250
4441
  };
4251
4442
  };
4252
- var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig) => {
4443
+ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enforceAuth, jwtConfig, rowPoliciesConfig, rlsClickhouseClient) => {
4253
4444
  const apiRequestHandler = await apiHandler(
4254
4445
  publicKey,
4255
4446
  clickhouseClient,
4256
4447
  temporalClient,
4257
4448
  enforceAuth,
4258
- jwtConfig
4449
+ jwtConfig,
4450
+ rowPoliciesConfig,
4451
+ rlsClickhouseClient
4259
4452
  );
4260
4453
  const webApps = await getWebApps2();
4261
4454
  const sortedWebApps = Array.from(webApps.values()).sort((a, b) => {
@@ -4277,30 +4470,22 @@ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enfor
4277
4470
  );
4278
4471
  return;
4279
4472
  }
4280
- let jwtPayload;
4281
- if (publicKey && jwtConfig) {
4282
- const jwt = req.headers.authorization?.split(" ")[1];
4283
- if (jwt) {
4284
- try {
4285
- const { payload } = await jose.jwtVerify(jwt, publicKey, {
4286
- issuer: jwtConfig.issuer,
4287
- audience: jwtConfig.audience
4288
- });
4289
- jwtPayload = payload;
4290
- } catch (error) {
4291
- console.log("JWT verification failed for WebApp route");
4292
- }
4293
- }
4294
- }
4473
+ const authResult = await authenticateRequest(
4474
+ req,
4475
+ res,
4476
+ publicKey,
4477
+ jwtConfig,
4478
+ enforceAuth,
4479
+ rowPoliciesConfig
4480
+ );
4481
+ if (!authResult) return;
4482
+ const { jwtPayload, rowPolicyOpts } = authResult;
4295
4483
  for (const webApp of sortedWebApps) {
4296
4484
  const mountPath = webApp.config.mountPath || "/";
4297
4485
  const normalizedMount = mountPath.endsWith("/") && mountPath !== "/" ? mountPath.slice(0, -1) : mountPath;
4298
4486
  const matches = pathname === normalizedMount || pathname.startsWith(normalizedMount + "/");
4299
4487
  if (matches) {
4300
- if (webApp.config.injectMooseUtils !== false) {
4301
- const { getMooseUtils: getMooseUtils2 } = await Promise.resolve().then(() => (init_standalone(), standalone_exports));
4302
- req.moose = await getMooseUtils2();
4303
- }
4488
+ const { getMooseUtils: getMooseUtils2, runWithRequestContext: runWithRequestContext2 } = await Promise.resolve().then(() => (init_standalone(), standalone_exports));
4304
4489
  let proxiedUrl = req.url;
4305
4490
  if (normalizedMount !== "/") {
4306
4491
  const pathWithoutMount = pathname.substring(normalizedMount.length) || "/";
@@ -4314,7 +4499,15 @@ var createMainRouter = async (publicKey, clickhouseClient, temporalClient, enfor
4314
4499
  url: proxiedUrl
4315
4500
  }
4316
4501
  );
4317
- await webApp.handler(modifiedReq, res);
4502
+ await runWithRequestContext2(
4503
+ { rowPolicyOpts, jwt: jwtPayload },
4504
+ async () => {
4505
+ if (webApp.config.injectMooseUtils !== false) {
4506
+ modifiedReq.moose = await getMooseUtils2();
4507
+ }
4508
+ await webApp.handler(modifiedReq, res);
4509
+ }
4510
+ );
4318
4511
  return;
4319
4512
  } catch (error) {
4320
4513
  console.error(`Error in WebApp ${webApp.name}:`, error);
@@ -4365,14 +4558,33 @@ var runApis = async (config) => {
4365
4558
  const clickhouseClient = getClickhouseClient(
4366
4559
  toClientConfig2(config.clickhouseConfig)
4367
4560
  );
4561
+ let rlsClickhouseClient;
4562
+ if (config.rowPoliciesConfig) {
4563
+ rlsClickhouseClient = getClickhouseClient(
4564
+ toClientConfig2({
4565
+ ...config.clickhouseConfig,
4566
+ username: config.clickhouseConfig.rlsUser ?? MOOSE_RLS_USER,
4567
+ password: config.clickhouseConfig.rlsPassword ?? config.clickhouseConfig.password
4568
+ })
4569
+ );
4570
+ }
4368
4571
  let publicKey;
4369
4572
  if (config.jwtConfig?.secret) {
4370
4573
  console.log("Importing JWT public key...");
4371
4574
  publicKey = await jose.importSPKI(config.jwtConfig.secret, "RS256");
4372
4575
  }
4576
+ if (config.rowPoliciesConfig && !publicKey) {
4577
+ console.error(
4578
+ "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."
4579
+ );
4580
+ }
4373
4581
  const runtimeQueryClient = new QueryClient(clickhouseClient, "runtime");
4374
4582
  globalThis._mooseRuntimeContext = {
4375
- client: new MooseClient(runtimeQueryClient, temporalClient)
4583
+ client: new MooseClient(runtimeQueryClient, temporalClient),
4584
+ clickhouseClient,
4585
+ rlsClickhouseClient,
4586
+ temporalClient,
4587
+ rowPoliciesConfig: config.rowPoliciesConfig
4376
4588
  };
4377
4589
  const server = import_http.default.createServer(
4378
4590
  await createMainRouter(
@@ -4380,7 +4592,9 @@ var runApis = async (config) => {
4380
4592
  clickhouseClient,
4381
4593
  temporalClient,
4382
4594
  config.enforceAuth,
4383
- config.jwtConfig
4595
+ config.jwtConfig,
4596
+ config.rowPoliciesConfig,
4597
+ rlsClickhouseClient
4384
4598
  )
4385
4599
  );
4386
4600
  const port = config.proxyPort !== void 0 ? config.proxyPort : 4001;
@@ -5391,7 +5605,10 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5391
5605
  "--worker-count <count>",
5392
5606
  "Number of worker processes for the consumption API cluster",
5393
5607
  parseInt
5394
- ).action(
5608
+ ).option(
5609
+ "--row-policies <json>",
5610
+ "JSON map of ClickHouse setting names to JWT claim names for row policy enforcement"
5611
+ ).option("--rls-user <user>", "ClickHouse username for RLS queries").option("--rls-password <password>", "ClickHouse password for RLS queries").action(
5395
5612
  (clickhouseDb, clickhouseHost, clickhousePort, clickhouseUsername, clickhousePassword, options) => {
5396
5613
  runApis({
5397
5614
  clickhouseConfig: {
@@ -5400,7 +5617,9 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5400
5617
  port: clickhousePort,
5401
5618
  username: clickhouseUsername,
5402
5619
  password: clickhousePassword,
5403
- useSSL: options.clickhouseUseSsl
5620
+ useSSL: options.clickhouseUseSsl,
5621
+ rlsUser: options.rlsUser,
5622
+ rlsPassword: options.rlsPassword
5404
5623
  },
5405
5624
  jwtConfig: {
5406
5625
  secret: options.jwtSecret,
@@ -5416,7 +5635,27 @@ program.command("consumption-apis").description("Run consumption APIs").argument
5416
5635
  } : void 0,
5417
5636
  enforceAuth: options.enforceAuth,
5418
5637
  proxyPort: options.proxyPort,
5419
- workerCount: options.workerCount
5638
+ workerCount: options.workerCount,
5639
+ rowPoliciesConfig: options.rowPolicies ? (() => {
5640
+ let parsed;
5641
+ try {
5642
+ parsed = JSON.parse(options.rowPolicies);
5643
+ } catch (e) {
5644
+ console.error(
5645
+ `Failed to parse --row-policies JSON: ${e.message}`
5646
+ );
5647
+ process.exit(1);
5648
+ }
5649
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !Object.values(parsed).every(
5650
+ (v) => typeof v === "string"
5651
+ )) {
5652
+ console.error(
5653
+ `Invalid --row-policies JSON: expected a flat object with string values`
5654
+ );
5655
+ process.exit(1);
5656
+ }
5657
+ return parsed;
5658
+ })() : void 0
5420
5659
  });
5421
5660
  }
5422
5661
  );