@objectstack/rest 9.9.1 → 9.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -489,7 +489,7 @@ function rowsToCsv(fields, rows, includeHeader) {
489
489
  return lines.join("\r\n") + (lines.length > 0 ? "\r\n" : "");
490
490
  }
491
491
  var RestServer = class {
492
- constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider) {
492
+ constructor(server, protocol, config = {}, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider) {
493
493
  /**
494
494
  * Short-TTL cache for `hostname → environmentId` (P1-4). `resolveByHostname`
495
495
  * is a control-plane lookup (typically a DB query) that otherwise runs on
@@ -520,6 +520,7 @@ var RestServer = class {
520
520
  this.sharingRulesServiceProvider = sharingRulesServiceProvider;
521
521
  this.i18nServiceProvider = i18nServiceProvider;
522
522
  this.analyticsServiceProvider = analyticsServiceProvider;
523
+ this.settingsServiceProvider = settingsServiceProvider;
523
524
  }
524
525
  /**
525
526
  * Resolve the protocol for a given request. When `environmentId` is present
@@ -809,6 +810,7 @@ var RestServer = class {
809
810
  }
810
811
  let userId;
811
812
  let tenantId;
813
+ let email;
812
814
  const keyPrincipal = await (0, import_core.resolveApiKeyPrincipal)(identityQl, headers).catch(() => void 0);
813
815
  if (keyPrincipal) {
814
816
  userId = keyPrincipal.userId;
@@ -819,6 +821,11 @@ var RestServer = class {
819
821
  if (!session?.user?.id) return void 0;
820
822
  userId = session.user.id;
821
823
  tenantId = session.session?.activeOrganizationId ?? void 0;
824
+ if (session.user?.email) email = String(session.user.email);
825
+ }
826
+ if (!email && identityQl && typeof identityQl.find === "function") {
827
+ const urows = await identityQl.find("sys_user", { where: { id: userId }, limit: 1, context: { isSystem: true } }).catch(() => []);
828
+ if (urows?.[0]?.email) email = String(urows[0].email);
822
829
  }
823
830
  try {
824
831
  let ql;
@@ -909,14 +916,34 @@ var RestServer = class {
909
916
  } catch {
910
917
  }
911
918
  }
919
+ let timezone;
920
+ let locale;
921
+ try {
922
+ const settings = this.settingsServiceProvider ? await this.settingsServiceProvider(environmentId).catch(() => void 0) : void 0;
923
+ if (settings && typeof settings.get === "function") {
924
+ const sctx = { tenantId, userId };
925
+ const [tzRes, localeRes] = await Promise.all([
926
+ settings.get("localization", "timezone", sctx).catch(() => void 0),
927
+ settings.get("localization", "locale", sctx).catch(() => void 0)
928
+ ]);
929
+ const tzVal = tzRes?.value;
930
+ const localeVal = localeRes?.value;
931
+ if (typeof tzVal === "string" && tzVal.trim()) timezone = tzVal.trim();
932
+ if (typeof localeVal === "string" && localeVal.trim()) locale = localeVal.trim();
933
+ }
934
+ } catch {
935
+ }
912
936
  return {
913
937
  userId,
914
938
  tenantId,
939
+ email,
915
940
  roles,
916
941
  permissions,
917
942
  systemPermissions,
918
943
  isSystem: false,
919
- org_user_ids
944
+ org_user_ids,
945
+ ...timezone ? { timezone } : {},
946
+ ...locale ? { locale } : {}
920
947
  };
921
948
  } catch {
922
949
  return void 0;
@@ -2983,6 +3010,7 @@ var RestServer = class {
2983
3010
  Object.assign(filteredData, rawBody);
2984
3011
  }
2985
3012
  const context = {
3013
+ publicFormGrant: { object: match.object },
2986
3014
  permissions: ["guest_portal"],
2987
3015
  anonymous: true
2988
3016
  };
@@ -4449,6 +4477,13 @@ function createRestApiPlugin(config = {}) {
4449
4477
  return void 0;
4450
4478
  }
4451
4479
  };
4480
+ const settingsServiceProvider = async (_environmentId) => {
4481
+ try {
4482
+ return ctx.getService("settings");
4483
+ } catch {
4484
+ return void 0;
4485
+ }
4486
+ };
4452
4487
  if (!server) {
4453
4488
  ctx.logger.warn(`RestApiPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);
4454
4489
  return;
@@ -4459,9 +4494,14 @@ function createRestApiPlugin(config = {}) {
4459
4494
  }
4460
4495
  ctx.logger.info("Hydrating REST API from Protocol...");
4461
4496
  try {
4462
- const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider);
4497
+ const restServer = new RestServer(server, protocol, config.api, kernelManager, envRegistry, defaultEnvironmentIdProvider, authServiceProvider, objectQLProvider, emailServiceProvider, sharingServiceProvider, reportsServiceProvider, approvalsServiceProvider, sharingRulesServiceProvider, i18nServiceProvider, analyticsServiceProvider, settingsServiceProvider);
4463
4498
  restServer.registerRoutes();
4464
4499
  ctx.logger.info("REST API successfully registered");
4500
+ if (!config.api?.requireAuth) {
4501
+ ctx.logger.warn(
4502
+ "[security] anonymous access to the data API is ALLOWED (api.requireAuth=false) \u2014 objects without OWD/RLS are world-readable. For secure-by-default set api.requireAuth=true and expose public records via share-links / publicSharing (ADR-0056 D2)."
4503
+ );
4504
+ }
4465
4505
  } catch (err) {
4466
4506
  ctx.logger.error("Failed to register REST API routes", { error: err.message });
4467
4507
  throw err;