@objectstack/core 7.8.0 → 8.0.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
@@ -1668,6 +1668,59 @@ declare class PluginSecurityScanner {
1668
1668
  shutdown(): void;
1669
1669
  }
1670
1670
 
1671
+ /** Default visible prefix for generated keys (helps users identify a key). */
1672
+ declare const API_KEY_PREFIX = "osk_";
1673
+ /**
1674
+ * Derive the at-rest hash for an API key. Inbound keys are hashed the same way
1675
+ * before the DB lookup. Because the lookup matches an indexed, high-entropy
1676
+ * hash exactly, this doubles as a constant-effort comparison: an attacker
1677
+ * cannot recover the raw key by probing for partial matches.
1678
+ */
1679
+ declare function hashApiKey(raw: string): string;
1680
+ /** Result of {@link generateApiKey}. `raw` is shown to the user only once. */
1681
+ interface GeneratedApiKey {
1682
+ /** The full secret to hand to the client. NEVER persist this. */
1683
+ raw: string;
1684
+ /** `sha256(raw)` hex — store this in `sys_api_key.key`. */
1685
+ hash: string;
1686
+ /** Short non-secret prefix for display/identification (`sys_api_key.prefix`). */
1687
+ prefix: string;
1688
+ }
1689
+ /**
1690
+ * Generate a fresh API key. Returns the raw secret (caller must surface it to
1691
+ * the user exactly once and then discard it), its at-rest hash, and a short
1692
+ * non-secret prefix for display.
1693
+ */
1694
+ declare function generateApiKey(prefix?: string): GeneratedApiKey;
1695
+ /**
1696
+ * Extract an API key from request headers. Accepts `X-API-Key: <token>` or
1697
+ * `Authorization: ApiKey <token>` (case-insensitive scheme). Bearer tokens are
1698
+ * deliberately NOT treated as API keys — those flow through the session path.
1699
+ */
1700
+ declare function extractApiKey(headers: any): string | undefined;
1701
+ /** Parse a `scopes` value that may be a JSON-string textarea or a real array. */
1702
+ declare function parseScopes(value: unknown): string[];
1703
+ /** Return true when an expiry timestamp is in the past (i.e. the key is dead). */
1704
+ declare function isExpired(value: unknown, nowMs: number): boolean;
1705
+ /** The principal resolved from a valid `sys_api_key`. */
1706
+ interface ApiKeyPrincipal {
1707
+ userId: string;
1708
+ tenantId?: string;
1709
+ scopes: string[];
1710
+ }
1711
+ /**
1712
+ * Verify an inbound API key against `sys_api_key` and resolve its principal.
1713
+ * This is the ONE verify path shared by the dispatcher/MCP and REST surfaces.
1714
+ *
1715
+ * Fail-closed: returns `undefined` for a missing key, an unusable data engine,
1716
+ * a lookup error, or a key that is unknown / revoked / expired / owner-less.
1717
+ *
1718
+ * @param ql A data engine with `find(object, { where, limit, context })`.
1719
+ * @param headers Request headers (Web `Headers` or a plain object).
1720
+ * @param nowMs Clock for expiry checks (injectable for tests).
1721
+ */
1722
+ declare function resolveApiKeyPrincipal(ql: any, headers: any, nowMs?: number): Promise<ApiKeyPrincipal | undefined>;
1723
+
1671
1724
  /**
1672
1725
  * Environment utilities for universal (Node/Browser) compatibility.
1673
1726
  */
@@ -2080,4 +2133,4 @@ declare class NamespaceResolver {
2080
2133
  private suggestAlternative;
2081
2134
  }
2082
2135
 
2083
- export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, 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 ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, generateEd25519KeyPair, getEnv, getMemoryUsage, isNode, parseSignature, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
2136
+ export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, 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 ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
package/dist/index.d.ts CHANGED
@@ -1668,6 +1668,59 @@ declare class PluginSecurityScanner {
1668
1668
  shutdown(): void;
1669
1669
  }
1670
1670
 
1671
+ /** Default visible prefix for generated keys (helps users identify a key). */
1672
+ declare const API_KEY_PREFIX = "osk_";
1673
+ /**
1674
+ * Derive the at-rest hash for an API key. Inbound keys are hashed the same way
1675
+ * before the DB lookup. Because the lookup matches an indexed, high-entropy
1676
+ * hash exactly, this doubles as a constant-effort comparison: an attacker
1677
+ * cannot recover the raw key by probing for partial matches.
1678
+ */
1679
+ declare function hashApiKey(raw: string): string;
1680
+ /** Result of {@link generateApiKey}. `raw` is shown to the user only once. */
1681
+ interface GeneratedApiKey {
1682
+ /** The full secret to hand to the client. NEVER persist this. */
1683
+ raw: string;
1684
+ /** `sha256(raw)` hex — store this in `sys_api_key.key`. */
1685
+ hash: string;
1686
+ /** Short non-secret prefix for display/identification (`sys_api_key.prefix`). */
1687
+ prefix: string;
1688
+ }
1689
+ /**
1690
+ * Generate a fresh API key. Returns the raw secret (caller must surface it to
1691
+ * the user exactly once and then discard it), its at-rest hash, and a short
1692
+ * non-secret prefix for display.
1693
+ */
1694
+ declare function generateApiKey(prefix?: string): GeneratedApiKey;
1695
+ /**
1696
+ * Extract an API key from request headers. Accepts `X-API-Key: <token>` or
1697
+ * `Authorization: ApiKey <token>` (case-insensitive scheme). Bearer tokens are
1698
+ * deliberately NOT treated as API keys — those flow through the session path.
1699
+ */
1700
+ declare function extractApiKey(headers: any): string | undefined;
1701
+ /** Parse a `scopes` value that may be a JSON-string textarea or a real array. */
1702
+ declare function parseScopes(value: unknown): string[];
1703
+ /** Return true when an expiry timestamp is in the past (i.e. the key is dead). */
1704
+ declare function isExpired(value: unknown, nowMs: number): boolean;
1705
+ /** The principal resolved from a valid `sys_api_key`. */
1706
+ interface ApiKeyPrincipal {
1707
+ userId: string;
1708
+ tenantId?: string;
1709
+ scopes: string[];
1710
+ }
1711
+ /**
1712
+ * Verify an inbound API key against `sys_api_key` and resolve its principal.
1713
+ * This is the ONE verify path shared by the dispatcher/MCP and REST surfaces.
1714
+ *
1715
+ * Fail-closed: returns `undefined` for a missing key, an unusable data engine,
1716
+ * a lookup error, or a key that is unknown / revoked / expired / owner-less.
1717
+ *
1718
+ * @param ql A data engine with `find(object, { where, limit, context })`.
1719
+ * @param headers Request headers (Web `Headers` or a plain object).
1720
+ * @param nowMs Clock for expiry checks (injectable for tests).
1721
+ */
1722
+ declare function resolveApiKeyPrincipal(ql: any, headers: any, nowMs?: number): Promise<ApiKeyPrincipal | undefined>;
1723
+
1671
1724
  /**
1672
1725
  * Environment utilities for universal (Node/Browser) compatibility.
1673
1726
  */
@@ -2080,4 +2133,4 @@ declare class NamespaceResolver {
2080
2133
  private suggestAlternative;
2081
2134
  }
2082
2135
 
2083
- export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, 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 ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, generateEd25519KeyPair, getEnv, getMemoryUsage, isNode, parseSignature, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
2136
+ export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, 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 ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isExpired, isNode, parseScopes, parseSignature, resolveApiKeyPrincipal, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
package/dist/index.js CHANGED
@@ -3884,6 +3884,109 @@ var PluginSecurityScanner = class {
3884
3884
  }
3885
3885
  };
3886
3886
 
3887
+ // src/security/api-key.ts
3888
+ import { createHash, randomBytes } from "crypto";
3889
+ var API_KEY_PREFIX = "osk_";
3890
+ var API_KEY_ENTROPY_BYTES = 32;
3891
+ var VISIBLE_PREFIX_LEN = 12;
3892
+ function hashApiKey(raw) {
3893
+ return createHash("sha256").update(raw, "utf8").digest("hex");
3894
+ }
3895
+ function generateApiKey(prefix = API_KEY_PREFIX) {
3896
+ const secret = randomBytes(API_KEY_ENTROPY_BYTES).toString("base64url");
3897
+ const raw = `${prefix}${secret}`;
3898
+ return {
3899
+ raw,
3900
+ hash: hashApiKey(raw),
3901
+ prefix: raw.slice(0, VISIBLE_PREFIX_LEN)
3902
+ };
3903
+ }
3904
+ function extractApiKey(headers) {
3905
+ const x = readHeader(headers, "x-api-key");
3906
+ if (x && x.trim()) return x.trim();
3907
+ const auth = readHeader(headers, "authorization");
3908
+ if (!auth) return void 0;
3909
+ const m = auth.match(/^ApiKey\s+(.+)$/i);
3910
+ const token = m?.[1]?.trim();
3911
+ return token || void 0;
3912
+ }
3913
+ function parseScopes(value) {
3914
+ if (Array.isArray(value)) {
3915
+ return value.filter((s) => typeof s === "string" && s.length > 0);
3916
+ }
3917
+ if (typeof value === "string" && value.trim()) {
3918
+ const parsed = safeJsonParse(value, []);
3919
+ if (Array.isArray(parsed)) {
3920
+ return parsed.filter((s) => typeof s === "string" && s.length > 0);
3921
+ }
3922
+ }
3923
+ return [];
3924
+ }
3925
+ function isExpired(value, nowMs) {
3926
+ if (value == null) return false;
3927
+ let ms;
3928
+ if (typeof value === "number") {
3929
+ ms = value < 1e12 ? value * 1e3 : value;
3930
+ } else if (value instanceof Date) {
3931
+ ms = value.getTime();
3932
+ } else if (typeof value === "string") {
3933
+ ms = Date.parse(value);
3934
+ } else {
3935
+ return false;
3936
+ }
3937
+ if (Number.isNaN(ms)) return false;
3938
+ return ms <= nowMs;
3939
+ }
3940
+ async function resolveApiKeyPrincipal(ql, headers, nowMs = Date.now()) {
3941
+ const apiKey = extractApiKey(headers);
3942
+ if (!apiKey) return void 0;
3943
+ if (!ql || typeof ql.find !== "function") return void 0;
3944
+ let rows;
3945
+ try {
3946
+ rows = await ql.find("sys_api_key", {
3947
+ where: { key: hashApiKey(apiKey), revoked: false },
3948
+ limit: 1,
3949
+ context: { isSystem: true }
3950
+ });
3951
+ } catch {
3952
+ return void 0;
3953
+ }
3954
+ if (rows && rows.value) rows = rows.value;
3955
+ const row = Array.isArray(rows) ? rows[0] : void 0;
3956
+ if (!row || row.revoked === true) return void 0;
3957
+ const expiresAt = row.expires_at ?? row.expiresAt;
3958
+ if (isExpired(expiresAt, nowMs)) return void 0;
3959
+ const userId = row.user_id ?? row.userId;
3960
+ if (!userId || typeof userId !== "string") return void 0;
3961
+ return {
3962
+ userId,
3963
+ tenantId: row.organization_id ?? row.organizationId ?? void 0,
3964
+ scopes: parseScopes(row.scopes)
3965
+ };
3966
+ }
3967
+ function readHeader(headers, name) {
3968
+ if (!headers) return void 0;
3969
+ const lower = name.toLowerCase();
3970
+ if (typeof headers.get === "function") {
3971
+ const v = headers.get(name) ?? headers.get(lower);
3972
+ return v == null ? void 0 : String(v);
3973
+ }
3974
+ for (const key of Object.keys(headers)) {
3975
+ if (key.toLowerCase() === lower) {
3976
+ const v = headers[key];
3977
+ return Array.isArray(v) ? v[0] : v == null ? void 0 : String(v);
3978
+ }
3979
+ }
3980
+ return void 0;
3981
+ }
3982
+ function safeJsonParse(s, fallback) {
3983
+ try {
3984
+ return JSON.parse(s);
3985
+ } catch {
3986
+ return fallback;
3987
+ }
3988
+ }
3989
+
3887
3990
  // src/health-monitor.ts
3888
3991
  var PluginHealthMonitor = class {
3889
3992
  constructor(logger) {
@@ -4127,7 +4230,7 @@ var PluginHealthMonitor = class {
4127
4230
  };
4128
4231
 
4129
4232
  // src/hot-reload.ts
4130
- import { createHash } from "crypto";
4233
+ import { createHash as createHash2 } from "crypto";
4131
4234
  var generateUUID = () => {
4132
4235
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
4133
4236
  return crypto.randomUUID();
@@ -4222,7 +4325,7 @@ var PluginStateManager = class {
4222
4325
  */
4223
4326
  calculateChecksum(state) {
4224
4327
  const stateStr = JSON.stringify(state);
4225
- return createHash("sha256").update(stateStr).digest("hex");
4328
+ return createHash2("sha256").update(stateStr).digest("hex");
4226
4329
  }
4227
4330
  /**
4228
4331
  * Shutdown state manager
@@ -4798,6 +4901,7 @@ var NamespaceResolver = class {
4798
4901
  }
4799
4902
  };
4800
4903
  export {
4904
+ API_KEY_PREFIX,
4801
4905
  ApiRegistry,
4802
4906
  CORE_FALLBACK_FACTORIES,
4803
4907
  DependencyResolver,
@@ -4831,11 +4935,17 @@ export {
4831
4935
  createMemoryQueue,
4832
4936
  createPluginConfigValidator,
4833
4937
  createPluginPermissionEnforcer,
4938
+ extractApiKey,
4939
+ generateApiKey,
4834
4940
  generateEd25519KeyPair,
4835
4941
  getEnv,
4836
4942
  getMemoryUsage,
4943
+ hashApiKey,
4944
+ isExpired,
4837
4945
  isNode,
4946
+ parseScopes,
4838
4947
  parseSignature,
4948
+ resolveApiKeyPrincipal,
4839
4949
  resolveLocale,
4840
4950
  safeExit,
4841
4951
  signPayload,