@mdbase-dev/connect 0.1.0-beta.92 → 0.1.0-beta.94

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.
@@ -1 +1 @@
1
- sha384-G9S7H692RFcmy7JJ8CrePVKzJb4fvERv7ZfJfsbaNMtdu0ffE1SRdTgZAyhrMtJk
1
+ sha384-kDjazWo3W17cBn4WByvZ0jGGc1aqQxNTWglefSAMrEoEzGRgo8YNzRYSI3sQb8tx
@@ -36,22 +36,16 @@ function effectiveCapability(id, required, manifest, connection) {
36
36
  reason: "The current grant does not include every operation for this capability."
37
37
  };
38
38
  }
39
- if (id === "definitions.contracts.current"
40
- && connection.scope.access !== "full_collection") {
41
- const approved = new Set(connection.scope.contracts.map(({ id, version, digest }) => `${id}@${version}:${digest}`));
42
- const missingContracts = (manifest.requirements?.contracts ?? []).filter(({ id, version, digest }) => !approved.has(`${id}@${version}:${digest}`));
43
- if (missingContracts.length > 0) {
44
- return {
45
- ...base,
46
- state: "requires_authorization",
47
- reason: "The current grant does not approve the exact required contract definitions.",
48
- evidence: [...base.evidence, {
49
- source: "authorization",
50
- fact: `Missing ${missingContracts.map(({ id, version }) => `${id}@${version}`).join(", ")}.`
51
- }],
52
- details: { missingContracts }
53
- };
54
- }
39
+ if (connection.scope.access !== "full_collection") {
40
+ return {
41
+ ...base,
42
+ state: "requires_authorization",
43
+ reason: "The current grant uses legacy contract scope and must be reauthorized for the entire collection.",
44
+ evidence: [...base.evidence, {
45
+ source: "authorization",
46
+ fact: "Legacy contract scope is compatibility evidence, not active data authority."
47
+ }]
48
+ };
55
49
  }
56
50
  if (id.startsWith("files.")) {
57
51
  const action = id.slice("files.".length);
package/dist/connect.js CHANGED
@@ -7,7 +7,7 @@ import { MdbaseConnection } from "./connection.js";
7
7
  import { addConnectionId, connectionIds, removeConnectionId } from "./connection-index.js";
8
8
  import { authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
9
9
  import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
10
- import { connectError, serverConnectError } from "./errors.js";
10
+ import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
11
11
  import { DEFAULT_OPERATIONS } from "./internal-types.js";
12
12
  import { prepareAuthorizationWindow, publishPopupAuthorizationCompletion, waitForPopupAuthorization } from "./popup-authorization.js";
13
13
  import { uniqueOperations } from "./operation-helpers.js";
@@ -607,15 +607,25 @@ export class MdbaseConnectInternals {
607
607
  }
608
608
  const previous = parseStored(this.storage.getItem(this.tokenKey(collectionId)));
609
609
  const retirement = planRetiredGrantKeys(this.storage, this.pendingMutationKey(collectionId), previous, keyHandle, this.grantKeyLeases.handles(collectionId), this.grantKeyLeases.retainAutomaticallyRetiredKeys);
610
- const token = storedTokenFromResponse({
611
- body,
612
- clientId,
613
- keyHandle,
614
- previous,
615
- retiredKeyHandles: retirement.retained,
616
- defaultApplicationOrigin: this.defaultApplicationOrigin(),
617
- pinConnectorIdentity: (connectorId, publicKey) => this.pinConnectorIdentity(connectorId, publicKey)
618
- });
610
+ let token;
611
+ try {
612
+ token = storedTokenFromResponse({
613
+ body,
614
+ clientId,
615
+ keyHandle,
616
+ previous,
617
+ retiredKeyHandles: retirement.retained,
618
+ defaultApplicationOrigin: this.defaultApplicationOrigin(),
619
+ pinConnectorIdentity: (connectorId, publicKey) => this.pinConnectorIdentity(connectorId, publicKey)
620
+ });
621
+ }
622
+ catch (error) {
623
+ if (error instanceof MdbaseConnectError
624
+ && error.code === "legacy_scope_reauthorization_required") {
625
+ this.removeToken(collectionId, keyHandle, error.code);
626
+ }
627
+ throw error;
628
+ }
619
629
  this.storage.setItem(this.tokenKey(collectionId), JSON.stringify(token));
620
630
  for (const handle of retirement.disposable)
621
631
  this.deleteGrantKeyWhenUnused(collectionId, handle);
@@ -3,7 +3,7 @@ import type { StoredToken } from "./internal-types.js";
3
3
  import type { ResolvedConnectTimeouts } from "./request-budget.js";
4
4
  export interface ConnectionTransportInternals {
5
5
  readonly relayEncryption: "required" | "disabled";
6
- removeToken(collectionId: string, keyHandle?: string, reason?: "not_authorized" | "authorization_lost" | "invalid_stored_grant", discardPending?: boolean): void;
6
+ removeToken(collectionId: string, keyHandle?: string, reason?: "not_authorized" | "authorization_lost" | "invalid_stored_grant" | "legacy_scope_reauthorization_required", discardPending?: boolean): void;
7
7
  storeTokenResponse(body: any, clientId: string, keyHandle?: string): StoredToken;
8
8
  acquireGrantKeyLease(collectionId: string, keyHandle: string, signal?: AbortSignal): Promise<() => void>;
9
9
  deleteGrantKeyWhenUnused(collectionId: string, keyHandle: string): void;
@@ -650,7 +650,7 @@ export class ConnectionTransport {
650
650
  stored: this.storage.getItem(this.internals.tokenKey(this.collectionId)),
651
651
  collectionId: this.collectionId,
652
652
  relayEncryption: this.internals.relayEncryption,
653
- invalidate: (keyHandle) => this.internals.removeToken(this.collectionId, keyHandle, "invalid_stored_grant"),
653
+ invalidate: (keyHandle, reason = "invalid_stored_grant") => this.internals.removeToken(this.collectionId, keyHandle, reason),
654
654
  directCapable: (token) => this.directCapable(token)
655
655
  });
656
656
  }
@@ -138,8 +138,9 @@ export interface QuerySummary {
138
138
  /** Application-facing form of the canonical mdbase v0.3 query schema. */
139
139
  export interface QueryInput {
140
140
  /**
141
- * Contract-scoped queries accept only `types`, `timezone`, pagination,
142
- * `frontmatterMode`, and `contract`; filter normalized fields in the app.
141
+ * Queries using a semantic contract view accept only `types`, `timezone`,
142
+ * pagination, `frontmatterMode`, and `contract`; filter normalized fields in
143
+ * the app.
143
144
  */
144
145
  types?: string[];
145
146
  /** IANA timezone used for calendar semantics in this invocation. */
@@ -166,7 +167,7 @@ export interface QueryInput {
166
167
  snapshot?: string;
167
168
  includeBody?: boolean;
168
169
  frontmatterMode?: "effective" | "persisted" | "both";
169
- /** Narrow a contract-scoped query to one exact contract/provider view. */
170
+ /** Select one exact semantic contract/provider view for this query. */
170
171
  contract?: DataContractSelector;
171
172
  }
172
173
  export interface QueryResult<Record extends JsonObject = JsonObject> {
@@ -18,14 +18,14 @@ export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied",
18
18
  export declare const ALL_CONNECT_PROBLEM_CODES: ConnectProblemCode[];
19
19
  export type RegistrationProblemCode = "discovery_failed" | "invalid_operation_response" | "invalid_application_manifest" | "invalid_request" | "manifest_load_failed" | "rate_limited" | "temporarily_unavailable" | "timeout";
20
20
  export declare const REGISTRATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout"];
21
- export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_declaration_mismatch" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
22
- export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
21
+ export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_declaration_mismatch" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "legacy_scope_reauthorization_required" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
22
+ export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "legacy_scope_reauthorization_required", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
23
23
  export type NotificationProblemCode = RegistrationProblemCode | "authorization_expired" | "invalid_operation_response" | "invalid_push_subscription" | "managed_fcm_not_declared" | "not_authorized" | "notification_criterion_not_declared" | "notification_reauthorization_required" | "notification_registration_failed" | "notification_unregistration_failed" | "notifications_not_declared" | "notifications_unavailable";
24
24
  export declare const NOTIFICATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "authorization_expired", "invalid_operation_response", "invalid_push_subscription", "managed_fcm_not_declared", "not_authorized", "notification_criterion_not_declared", "notification_reauthorization_required", "notification_registration_failed", "notification_unregistration_failed", "notifications_not_declared", "notifications_unavailable"];
25
25
  export type DirectAccessProblemCode = "connector_offline" | "connector_upgrade_required" | "invalid_operation_response" | "not_authorized" | "operation_failed" | "temporarily_unavailable" | "timeout";
26
26
  export declare const DIRECT_ACCESS_PROBLEM_CODES: readonly ["connector_offline", "connector_upgrade_required", "invalid_operation_response", "not_authorized", "operation_failed", "temporarily_unavailable", "timeout"];
27
27
  export type SessionProblemCode = AuthorizationProblemCode | "collection_not_ready" | "collection_not_selected" | "operation_cancelled" | "session_destroyed" | "session_not_started" | "session_starting" | "unknown_collection";
28
- export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "operation_cancelled", "session_destroyed", "session_not_started", "session_starting", "unknown_collection"];
28
+ export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "legacy_scope_reauthorization_required", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "operation_cancelled", "session_destroyed", "session_not_started", "session_starting", "unknown_collection"];
29
29
  export type CollectionSetupProblemCode = "collection_configuration_invalid" | "collection_invalid" | "collection_type_registry_invalid" | "collection_version_unsupported";
30
30
  export type CollectionDescriptionProblemCode = CommonOperationProblemCode | CollectionSetupProblemCode;
31
31
  export type CollectionReadProblemCode = CollectionDescriptionProblemCode | "invalid_path" | "operation_invalid";
package/dist/outcomes.js CHANGED
@@ -78,6 +78,7 @@ export const AUTHORIZATION_PROBLEM_CODES = [
78
78
  "invalid_callback",
79
79
  "invalid_device_authorization_response",
80
80
  "invalid_token_response",
81
+ "legacy_scope_reauthorization_required",
81
82
  "scope_denied",
82
83
  "reconnect_required",
83
84
  "token_exchange_failed",
@@ -20,7 +20,10 @@ export declare function validFileCapability(value: unknown): value is FileCapabi
20
20
  export declare function validStoredEncryption(encryption: StoredToken["encryption"], collectionId: string): encryption is GrantEncryption;
21
21
  export declare function parseDeviceAuthorization(body: any): MdbaseDeviceAuthorization;
22
22
  export declare function parseStored<T>(value: string | null): T | null;
23
+ /** Parse both canonical and N-1 grant shapes so callers can diagnose legacy grants. */
23
24
  export declare function parseGrantScope(value: unknown): GrantScope | null;
25
+ /** Only this scope authorizes application data access. */
26
+ export declare function isCanonicalGrantScope(scope: GrantScope): boolean;
24
27
  export declare function stripTrailingSlash(value: string): string;
25
28
  export declare function defaultManifestSource(): string;
26
29
  export declare function defaultRedirectUri(): string;
@@ -215,6 +215,7 @@ export function parseStored(value) {
215
215
  return null;
216
216
  }
217
217
  }
218
+ /** Parse both canonical and N-1 grant shapes so callers can diagnose legacy grants. */
218
219
  export function parseGrantScope(value) {
219
220
  if (!value || typeof value !== "object" || Array.isArray(value))
220
221
  return null;
@@ -246,6 +247,10 @@ export function parseGrantScope(value) {
246
247
  return null;
247
248
  return scope;
248
249
  }
250
+ /** Only this scope authorizes application data access. */
251
+ export function isCanonicalGrantScope(scope) {
252
+ return scope.access === "full_collection" && scope.contracts.length === 0;
253
+ }
249
254
  export function stripTrailingSlash(value) {
250
255
  return value.endsWith("/") ? value.slice(0, -1) : value;
251
256
  }
package/dist/session.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface MdbaseSessionOptions {
17
17
  operations?: CollectionOperation[];
18
18
  autoSelect?: "only" | "never";
19
19
  }
20
- export type MdbaseUnavailableReason = "not_authorized" | "authorization_lost" | "invalid_stored_grant";
20
+ export type MdbaseUnavailableReason = "not_authorized" | "authorization_lost" | "invalid_stored_grant" | "legacy_scope_reauthorization_required";
21
21
  export type MdbaseSessionSnapshot<Frontmatter extends JsonObject = JsonObject> = {
22
22
  status: "not_started";
23
23
  connections: MdbaseConnectionInfo[];
@@ -3,7 +3,7 @@ interface ReadStoredTokenOptions {
3
3
  stored: string | null;
4
4
  collectionId: string;
5
5
  relayEncryption: "required" | "disabled";
6
- invalidate(keyHandle?: string): void;
6
+ invalidate(keyHandle?: string, reason?: "invalid_stored_grant" | "legacy_scope_reauthorization_required"): void;
7
7
  directCapable(token: StoredToken): boolean;
8
8
  }
9
9
  export declare function readStoredToken({ stored, collectionId, relayEncryption, invalidate, directCapable }: ReadStoredTokenOptions): StoredToken | null;
@@ -1,8 +1,8 @@
1
- import { parseGrantScope, parseStored, validFileCapability, validStoredAuthority, validStoredEncryption } from "./runtime-utils.js";
1
+ import { isCanonicalGrantScope, parseGrantScope, parseStored, validFileCapability, validStoredAuthority, validStoredEncryption } from "./runtime-utils.js";
2
2
  export function readStoredToken({ stored, collectionId, relayEncryption, invalidate, directCapable }) {
3
3
  const token = parseStored(stored);
4
- const reject = (keyHandle) => {
5
- invalidate(typeof keyHandle === "string" ? keyHandle : undefined);
4
+ const reject = (keyHandle, reason = "invalid_stored_grant") => {
5
+ invalidate(typeof keyHandle === "string" ? keyHandle : undefined, reason);
6
6
  return null;
7
7
  };
8
8
  if (!token) {
@@ -27,8 +27,12 @@ export function readStoredToken({ stored, collectionId, relayEncryption, invalid
27
27
  || (token.refreshExpiresAt !== undefined
28
28
  && (typeof token.refreshExpiresAt !== "number" || !Number.isFinite(token.refreshExpiresAt))))
29
29
  return reject(token.keyHandle);
30
- if (!parseGrantScope(token.scope))
30
+ const scope = parseGrantScope(token.scope);
31
+ if (!scope)
31
32
  return reject(token.keyHandle);
33
+ if (!isCanonicalGrantScope(scope)) {
34
+ return reject(token.keyHandle, "legacy_scope_reauthorization_required");
35
+ }
32
36
  if (token.fileCapability && !validFileCapability(token.fileCapability)) {
33
37
  return reject(token.keyHandle);
34
38
  }
@@ -1,6 +1,6 @@
1
1
  import { validateGrantEncryption } from "./crypto.js";
2
2
  import { connectError } from "./errors.js";
3
- import { parseGrantScope, validAuthorityTokenResponse, validFileCapability } from "./runtime-utils.js";
3
+ import { isCanonicalGrantScope, parseGrantScope, validAuthorityTokenResponse, validFileCapability } from "./runtime-utils.js";
4
4
  export function storedTokenFromResponse({ body, clientId, keyHandle, previous, retiredKeyHandles, defaultApplicationOrigin, pinConnectorIdentity }) {
5
5
  const collectionId = body.collection_id;
6
6
  if (typeof collectionId !== "string") {
@@ -10,6 +10,9 @@ export function storedTokenFromResponse({ body, clientId, keyHandle, previous, r
10
10
  if (!scope) {
11
11
  throw connectError("invalid_token_response", "Authorization returned no valid collection scope.");
12
12
  }
13
+ if (!isCanonicalGrantScope(scope)) {
14
+ throw connectError("legacy_scope_reauthorization_required", "Authorization returned a legacy contract-scoped grant. Reauthorize for the entire collection.");
15
+ }
13
16
  if (body.authority && !validAuthorityTokenResponse(body.authority, collectionId)) {
14
17
  throw connectError("invalid_token_response", "Authorization returned an invalid remote authority capability.");
15
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.92",
3
+ "version": "0.1.0-beta.94",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "README.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@mdbase-dev/connect-protocol": "0.1.0-beta.92"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.94"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",