@korajs/server 1.0.0-beta.10 → 1.0.0-beta.11

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
@@ -35,6 +35,18 @@ type RoutePredicate = Record<string, RoutePredicateOperators>;
35
35
  * Per-collection scope map from auth context.
36
36
  */
37
37
  type ScopeMap = Record<string, Record<string, unknown>>;
38
+ declare const DEFAULT_MAX_SCOPE_PREDICATE_VALUES = 100;
39
+ /** Canonicalize bounded `$in` predicates so equivalent authorization has one signature. */
40
+ declare function normalizeScopeMap(scopes: ScopeMap, maxValues?: number): ScopeMap;
41
+ /**
42
+ * Returns true if an operation is visible to a session based on its scopes.
43
+ *
44
+ * Rules:
45
+ * - No scopes configured => visible
46
+ * - Collection missing from scope map => hidden
47
+ * - All scoped field/value pairs must match the operation snapshot
48
+ */
49
+ declare function operationMatchesScopes(op: Operation, scopes: ScopeMap | undefined, fullRecord?: Record<string, unknown> | null): boolean;
38
50
 
39
51
  /**
40
52
  * A materialized record reconstructed from the operation log
@@ -2011,4 +2023,4 @@ declare class AwarenessRelay {
2011
2023
  */
2012
2024
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
2013
2025
 
2014
- export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, type OperationDecision, type OperationRejection, type OperationValidationContext, type OperationValidator, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, RETRIABLE_REJECTION_CODES, type RelayCallback, type RouteApplyResult, type RouteConditionalApply, type RouteConditionalResult, type RouteMutation, type RoutePredicate, type RoutePredicateOperators, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore, isRetriableRejection };
2026
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, DEFAULT_MAX_SCOPE_PREDICATE_VALUES, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, type OperationDecision, type OperationRejection, type OperationValidationContext, type OperationValidator, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, RETRIABLE_REJECTION_CODES, type RelayCallback, type RouteApplyResult, type RouteConditionalApply, type RouteConditionalResult, type RouteMutation, type RoutePredicate, type RoutePredicateOperators, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore, isRetriableRejection, normalizeScopeMap, operationMatchesScopes };
package/dist/index.d.ts CHANGED
@@ -35,6 +35,18 @@ type RoutePredicate = Record<string, RoutePredicateOperators>;
35
35
  * Per-collection scope map from auth context.
36
36
  */
37
37
  type ScopeMap = Record<string, Record<string, unknown>>;
38
+ declare const DEFAULT_MAX_SCOPE_PREDICATE_VALUES = 100;
39
+ /** Canonicalize bounded `$in` predicates so equivalent authorization has one signature. */
40
+ declare function normalizeScopeMap(scopes: ScopeMap, maxValues?: number): ScopeMap;
41
+ /**
42
+ * Returns true if an operation is visible to a session based on its scopes.
43
+ *
44
+ * Rules:
45
+ * - No scopes configured => visible
46
+ * - Collection missing from scope map => hidden
47
+ * - All scoped field/value pairs must match the operation snapshot
48
+ */
49
+ declare function operationMatchesScopes(op: Operation, scopes: ScopeMap | undefined, fullRecord?: Record<string, unknown> | null): boolean;
38
50
 
39
51
  /**
40
52
  * A materialized record reconstructed from the operation log
@@ -2011,4 +2023,4 @@ declare class AwarenessRelay {
2011
2023
  */
2012
2024
  declare function createKoraServer(config: KoraSyncServerConfig): KoraSyncServer;
2013
2025
 
2014
- export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, type OperationDecision, type OperationRejection, type OperationValidationContext, type OperationValidator, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, RETRIABLE_REJECTION_CODES, type RelayCallback, type RouteApplyResult, type RouteConditionalApply, type RouteConditionalResult, type RouteMutation, type RoutePredicate, type RoutePredicateOperators, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore, isRetriableRejection };
2026
+ export { type AuthContext, type AuthProvider, AwarenessRelay, type AwarenessRelayCallback, type ClientMetrics, ClientSession, type ClientSessionOptions, type CollectionQueryOptions, DEFAULT_MAX_SCOPE_PREDICATE_VALUES, type HttpPollResponse, HttpServerTransport, type HttpSyncRequest, type HttpSyncResponse, KoraAuthProvider, type KoraAuthProviderOptions, KoraSyncServer, type KoraSyncServerConfig, type LogEntry, type LogLevel, type Logger, type MaterializedRecord, MemoryServerStore, MixedAuthProvider, type MixedAuthProviderOptions, NoAuthProvider, type OperationDecision, type OperationRejection, type OperationValidationContext, type OperationValidator, PostgresServerStore, type ProductionHttpRoute, type ProductionHttpRouteContext, type ProductionHttpRouteRequest, type ProductionHttpRouteResponse, type ProductionServer, type ProductionServerConfig, RETRIABLE_REJECTION_CODES, type RelayCallback, type RouteApplyResult, type RouteConditionalApply, type RouteConditionalResult, type RouteMutation, type RoutePredicate, type RoutePredicateOperators, type RouteScopeOptions, ServerCloseHandler, ServerErrorHandler, ServerMessageHandler, ServerMetricsCollector, type ServerMetricsSnapshot, type ServerStatus, type ServerStore, ServerTransport, type SessionState, SqliteServerStore, TokenAuthProvider, type TokenAuthProviderOptions, type WsServerConstructor, type WsServerLike, WsServerTransport, type WsServerTransportOptions, type WsWebSocket, createDefaultLogger, createJsonLogger, createKoraServer, createPostgresServerStore, createPrettyLogger, createProductionServer, createSilentLogger, createSqliteServerStore, isRetriableRejection, normalizeScopeMap, operationMatchesScopes };
package/dist/index.js CHANGED
@@ -2523,6 +2523,39 @@ function mergeScopeMaps(base, overlay, source) {
2523
2523
  }
2524
2524
 
2525
2525
  // src/scopes/server-scope-filter.ts
2526
+ var DEFAULT_MAX_SCOPE_PREDICATE_VALUES = 100;
2527
+ function normalizeScopeMap(scopes, maxValues = DEFAULT_MAX_SCOPE_PREDICATE_VALUES) {
2528
+ const normalized = {};
2529
+ for (const collection of Object.keys(scopes).sort()) {
2530
+ const predicate = {};
2531
+ for (const field of Object.keys(scopes[collection] ?? {}).sort()) {
2532
+ const expected = scopes[collection]?.[field];
2533
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && "$in" in expected) {
2534
+ const values = expected.$in;
2535
+ if (!Array.isArray(values))
2536
+ throw new Error(`Invalid $in predicate for ${collection}.${field}`);
2537
+ const unique = [...new Map(values.map((value) => [stableValueKey(value), value])).values()];
2538
+ if (unique.length > maxValues)
2539
+ throw new Error(
2540
+ `Scope predicate for ${collection}.${field} exceeds the ${maxValues}-value limit`
2541
+ );
2542
+ predicate[field] = {
2543
+ $in: unique.sort((a, b) => stableValueKey(a).localeCompare(stableValueKey(b)))
2544
+ };
2545
+ } else predicate[field] = expected;
2546
+ }
2547
+ normalized[collection] = predicate;
2548
+ }
2549
+ return normalized;
2550
+ }
2551
+ function stableValueKey(value) {
2552
+ if (Array.isArray(value)) return `[${value.map(stableValueKey).join(",")}]`;
2553
+ if (value && typeof value === "object")
2554
+ return `{${Object.keys(value).sort().map(
2555
+ (key) => `${JSON.stringify(key)}:${stableValueKey(value[key])}`
2556
+ ).join(",")}}`;
2557
+ return JSON.stringify(value);
2558
+ }
2526
2559
  function operationMatchesScopes(op, scopes, fullRecord) {
2527
2560
  if (!scopes) return true;
2528
2561
  const collectionScope = scopes[op.collection];
@@ -2531,12 +2564,19 @@ function operationMatchesScopes(op, scopes, fullRecord) {
2531
2564
  const snapshot = buildSnapshot(op, fullRecord);
2532
2565
  if (!snapshot) return false;
2533
2566
  for (const [field, expected] of Object.entries(collectionScope)) {
2534
- if (snapshot[field] !== expected) {
2567
+ if (!matchesPredicate(snapshot[field], expected)) {
2535
2568
  return false;
2536
2569
  }
2537
2570
  }
2538
2571
  return true;
2539
2572
  }
2573
+ function matchesPredicate(actual, expected) {
2574
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && "$in" in expected) {
2575
+ const values = expected.$in;
2576
+ return Array.isArray(values) && values.some((value) => Object.is(actual, value));
2577
+ }
2578
+ return Object.is(actual, expected);
2579
+ }
2540
2580
  function missingScopeFields(op, scopes) {
2541
2581
  if (!scopes) return [];
2542
2582
  const collectionScope = scopes[op.collection];
@@ -2653,7 +2693,8 @@ function stableStringify(value) {
2653
2693
  return JSON.stringify(value);
2654
2694
  }
2655
2695
  function sameScopeMap(a, b) {
2656
- return stableStringify(a ?? null) === stableStringify(b ?? null);
2696
+ const normalize = (value) => value && typeof value === "object" ? normalizeScopeMap(value) : value;
2697
+ return stableStringify(normalize(a) ?? null) === stableStringify(normalize(b) ?? null);
2657
2698
  }
2658
2699
  var ClientSession = class _ClientSession {
2659
2700
  state = "connected";
@@ -3023,10 +3064,24 @@ var ClientSession = class _ClientSession {
3023
3064
  this.authContext = context;
3024
3065
  this.state = "authenticated";
3025
3066
  }
3026
- const resolvedScopes = resolveSessionScopes(this.store.getSchema(), {
3067
+ const rawResolvedScopes = resolveSessionScopes(this.store.getSchema(), {
3027
3068
  handshakeScope: msg.syncScope,
3028
3069
  authScopes: this.authContext?.scopes
3029
3070
  });
3071
+ let resolvedScopes;
3072
+ try {
3073
+ resolvedScopes = rawResolvedScopes ? normalizeScopeMap(rawResolvedScopes) : void 0;
3074
+ } catch (error) {
3075
+ this.sendToClient({
3076
+ type: "error",
3077
+ messageId: generateUUIDv74(),
3078
+ code: "SCOPE_PREDICATE_LIMIT",
3079
+ message: error instanceof Error ? error.message : "Invalid scope predicate",
3080
+ retriable: false
3081
+ });
3082
+ this.close("invalid scope predicate");
3083
+ return;
3084
+ }
3030
3085
  if (resolvedScopes) {
3031
3086
  if (this.authContext) {
3032
3087
  this.authContext = { ...this.authContext, scopes: resolvedScopes };
@@ -5519,6 +5574,7 @@ function formatUptime(ms) {
5519
5574
  export {
5520
5575
  AwarenessRelay,
5521
5576
  ClientSession,
5577
+ DEFAULT_MAX_SCOPE_PREDICATE_VALUES,
5522
5578
  HttpServerTransport,
5523
5579
  KoraAuthProvider,
5524
5580
  KoraSyncServer,
@@ -5538,6 +5594,8 @@ export {
5538
5594
  createProductionServer,
5539
5595
  createSilentLogger,
5540
5596
  createSqliteServerStore,
5541
- isRetriableRejection
5597
+ isRetriableRejection,
5598
+ normalizeScopeMap,
5599
+ operationMatchesScopes
5542
5600
  };
5543
5601
  //# sourceMappingURL=index.js.map