@objectstack/core 11.0.0 → 11.1.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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Logger, IServiceRegistry } from '@objectstack/spec/contracts';
2
- export { DriverInterface, IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, Logger, Middleware, RouteHandler } from '@objectstack/spec/contracts';
2
+ export { IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, Logger, Middleware, RouteHandler } from '@objectstack/spec/contracts';
3
3
  import { z } from 'zod';
4
4
  import { LoggerConfig } from '@objectstack/spec/system';
5
5
  import { ObjectLogger } from './logger.cjs';
@@ -1778,6 +1778,36 @@ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Pr
1778
1778
  currency?: string;
1779
1779
  }>;
1780
1780
 
1781
+ /**
1782
+ * ADR-0069 — authentication-policy session gate.
1783
+ *
1784
+ * Some auth policies (password expiry, enforced MFA) must block an
1785
+ * authenticated user from PROTECTED RESOURCES until they remediate, while
1786
+ * still letting them reach the auth endpoints (change-password, two-factor
1787
+ * enrollment, sign-out) and a few UI-bootstrap reads.
1788
+ *
1789
+ * The posture is computed ONCE, in the auth `customSession` enrichment, and
1790
+ * attached to the session user as `user.authGate = { code, message }`. The
1791
+ * transport seams (REST middleware, dispatcher) then call
1792
+ * {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping
1793
+ * the allow-list + decision in one pure function means the seams can never
1794
+ * drift on what is blocked.
1795
+ */
1796
+ interface AuthGate {
1797
+ /** Stable machine code, e.g. `PASSWORD_EXPIRED` / `MFA_REQUIRED`. */
1798
+ code: string;
1799
+ /** Human-facing message. */
1800
+ message: string;
1801
+ }
1802
+ /** True when `path` is exempt from the auth gate (auth + remediation + health). */
1803
+ declare function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean;
1804
+ /**
1805
+ * Returns the active gate when `sessionUser` carries an `authGate` AND `path`
1806
+ * is not allow-listed; otherwise null. Anonymous users (no `authGate`) and
1807
+ * allow-listed paths always pass.
1808
+ */
1809
+ declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
1810
+
1781
1811
  /**
1782
1812
  * Environment utilities for universal (Node/Browser) compatibility.
1783
1813
  */
@@ -2225,4 +2255,4 @@ declare class NamespaceResolver {
2225
2255
  private suggestAlternative;
2226
2256
  }
2227
2257
 
2228
- export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
2258
+ export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Logger, IServiceRegistry } from '@objectstack/spec/contracts';
2
- export { DriverInterface, IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, Logger, Middleware, RouteHandler } from '@objectstack/spec/contracts';
2
+ export { IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, Logger, Middleware, RouteHandler } from '@objectstack/spec/contracts';
3
3
  import { z } from 'zod';
4
4
  import { LoggerConfig } from '@objectstack/spec/system';
5
5
  import { ObjectLogger } from './logger.js';
@@ -1778,6 +1778,36 @@ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Pr
1778
1778
  currency?: string;
1779
1779
  }>;
1780
1780
 
1781
+ /**
1782
+ * ADR-0069 — authentication-policy session gate.
1783
+ *
1784
+ * Some auth policies (password expiry, enforced MFA) must block an
1785
+ * authenticated user from PROTECTED RESOURCES until they remediate, while
1786
+ * still letting them reach the auth endpoints (change-password, two-factor
1787
+ * enrollment, sign-out) and a few UI-bootstrap reads.
1788
+ *
1789
+ * The posture is computed ONCE, in the auth `customSession` enrichment, and
1790
+ * attached to the session user as `user.authGate = { code, message }`. The
1791
+ * transport seams (REST middleware, dispatcher) then call
1792
+ * {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping
1793
+ * the allow-list + decision in one pure function means the seams can never
1794
+ * drift on what is blocked.
1795
+ */
1796
+ interface AuthGate {
1797
+ /** Stable machine code, e.g. `PASSWORD_EXPIRED` / `MFA_REQUIRED`. */
1798
+ code: string;
1799
+ /** Human-facing message. */
1800
+ message: string;
1801
+ }
1802
+ /** True when `path` is exempt from the auth gate (auth + remediation + health). */
1803
+ declare function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean;
1804
+ /**
1805
+ * Returns the active gate when `sessionUser` carries an `authGate` AND `path`
1806
+ * is not allow-listed; otherwise null. Anonymous users (no `authGate`) and
1807
+ * allow-listed paths always pass.
1808
+ */
1809
+ declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
1810
+
1781
1811
  /**
1782
1812
  * Environment utilities for universal (Node/Browser) compatibility.
1783
1813
  */
@@ -2225,4 +2255,4 @@ declare class NamespaceResolver {
2225
2255
  private suggestAlternative;
2226
2256
  }
2227
2257
 
2228
- export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
2258
+ export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
package/dist/index.js CHANGED
@@ -4057,9 +4057,19 @@ async function resolveAuthzContext(input) {
4057
4057
  ctx.userId = userId;
4058
4058
  if (tenantId) ctx.tenantId = tenantId;
4059
4059
  if (!ql || typeof ql.find !== "function") return ctx;
4060
+ let userRowLoaded = false;
4061
+ let userRow;
4062
+ const getUserRow = async () => {
4063
+ if (!userRowLoaded) {
4064
+ userRowLoaded = true;
4065
+ const rows = await tryFind(ql, "sys_user", { id: userId }, 1);
4066
+ userRow = rows[0];
4067
+ }
4068
+ return userRow;
4069
+ };
4060
4070
  if (!ctx.email) {
4061
- const userRows = await tryFind(ql, "sys_user", { id: userId }, 1);
4062
- if (userRows[0]?.email) ctx.email = String(userRows[0].email);
4071
+ const u = await getUserRow();
4072
+ if (u?.email) ctx.email = String(u.email);
4063
4073
  }
4064
4074
  const memberWhere = tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId };
4065
4075
  const members = await tryFind(ql, "sys_member", memberWhere, 50);
@@ -4140,8 +4150,7 @@ async function resolveAuthzContext(input) {
4140
4150
  ctx.roles.unshift(BUILTIN_ROLE_PLATFORM_ADMIN);
4141
4151
  }
4142
4152
  if (!ctx.permissions.includes("ai_seat")) {
4143
- const seatRows = await tryFind(ql, "sys_user", { id: userId }, 1);
4144
- const aiAccess = seatRows?.[0]?.ai_access;
4153
+ const aiAccess = (await getUserRow())?.ai_access;
4145
4154
  if (aiAccess === true || aiAccess === 1 || aiAccess === "1") ctx.permissions.push("ai_seat");
4146
4155
  }
4147
4156
  return ctx;
@@ -4183,13 +4192,45 @@ async function resolveLocalizationContext(input) {
4183
4192
  }
4184
4193
  } catch {
4185
4194
  }
4186
- const tzRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "timezone", scope: "tenant" }, 1);
4187
- const localeRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "locale", scope: "tenant" }, 1);
4188
- const currencyRows = await tryFind(ql, "sys_setting", { namespace: "localization", key: "currency", scope: "tenant" }, 1);
4195
+ const rows = await tryFind(
4196
+ ql,
4197
+ "sys_setting",
4198
+ { namespace: "localization", key: { $in: ["timezone", "locale", "currency"] }, scope: "tenant" },
4199
+ 10
4200
+ );
4201
+ const valueOf = (k) => rows.find((r) => r.key === k)?.value;
4202
+ return {
4203
+ timezone: coerceTimeZone(valueOf("timezone")) ?? "UTC",
4204
+ locale: coerceLocale(valueOf("locale")) ?? "en-US",
4205
+ currency: coerceCurrency(valueOf("currency"))
4206
+ };
4207
+ }
4208
+
4209
+ // src/security/auth-gate.ts
4210
+ var ALLOW_PREFIXES = ["/api/v1/auth/", "/api/auth/", "/auth/"];
4211
+ var ALLOW_SUFFIXES = ["/health", "/ready", "/discovery", "/me/apps", "/me/localization"];
4212
+ function isAuthGateAllowlisted(rawPath) {
4213
+ if (!rawPath) return true;
4214
+ let path = rawPath.split("?")[0] || "/";
4215
+ let end = path.length;
4216
+ while (end > 1 && path.charCodeAt(end - 1) === 47) end--;
4217
+ path = path.slice(0, end) || "/";
4218
+ if (path.includes("/auth/")) return true;
4219
+ for (const p of ALLOW_PREFIXES) {
4220
+ if (path.startsWith(p) || path === p.replace(/\/$/, "")) return true;
4221
+ }
4222
+ for (const s of ALLOW_SUFFIXES) {
4223
+ if (path.endsWith(s)) return true;
4224
+ }
4225
+ return false;
4226
+ }
4227
+ function evaluateAuthGate(sessionUser, path) {
4228
+ const gate = sessionUser?.authGate;
4229
+ if (!gate || typeof gate.code !== "string") return null;
4230
+ if (isAuthGateAllowlisted(path)) return null;
4189
4231
  return {
4190
- timezone: coerceTimeZone(tzRows[0]?.value) ?? "UTC",
4191
- locale: coerceLocale(localeRows[0]?.value) ?? "en-US",
4192
- currency: coerceCurrency(currencyRows[0]?.value)
4232
+ code: gate.code,
4233
+ message: typeof gate.message === "string" && gate.message ? gate.message : "Access is blocked by an authentication policy."
4193
4234
  };
4194
4235
  }
4195
4236
 
@@ -5168,12 +5209,14 @@ export {
5168
5209
  createMemoryQueue,
5169
5210
  createPluginConfigValidator,
5170
5211
  createPluginPermissionEnforcer,
5212
+ evaluateAuthGate,
5171
5213
  extractApiKey,
5172
5214
  generateApiKey,
5173
5215
  generateEd25519KeyPair,
5174
5216
  getEnv,
5175
5217
  getMemoryUsage,
5176
5218
  hashApiKey,
5219
+ isAuthGateAllowlisted,
5177
5220
  isExpired,
5178
5221
  isNode,
5179
5222
  parseScopes,