import SyncEnvelopeReplayService from "./sync-envelope-replay-service.js"; export type SignedSyncMutation = import("./device-identity.js").SignedSyncMutation; export type SignedOfflineGrant = import("./offline-grant.js").SignedOfflineGrant; /** * @typedef {import("./device-identity.js").SignedSyncMutation} SignedSyncMutation * @typedef {import("./offline-grant.js").SignedOfflineGrant} SignedOfflineGrant */ /** * Sync-envelope replay service that authenticates via backend-signed device * certificates and signed offline grants instead of online session tokens. * * This is the generic Velocious primitive for long-offline and peer-forwarded * mutations: the HTTP uploader may differ from the mutation actor, but replay * authority is derived from the signed envelope and grant, not the uploader's * session. * * Every mutation is validated against the current sync manifest (the same * contract as the controller's sync replay endpoint): the model and operation * must be enabled in the current manifest, the grant resource entry must be * enabled and list the operation, and the grant policy hash must equal both the * mutation policy hash and the current manifest policy hash. Routed resources * are authorized through an actor/grant-scoped ability built by the configured * `abilityFactory`; without one, routed signed replay fails closed. */ export default class SignedSyncEnvelopeReplayService extends SyncEnvelopeReplayService { abilityFactory: ((args: { actor: ReturnType; configuration: import("../configuration.js").default | null; grant: import("./offline-grant.js").OfflineGrant; }) => Promise | import("../authorization/ability.js").default | null) | null; backendPublicKey: import("node:crypto").webcrypto.JsonWebKey; offlineGrantSigningKeys: import("./offline-grant.js").OfflineGrantSigningKey[]; actorLookup: ((userId: string) => Promise<{ id: () => string; } | null>) | null; /** * Creates a signed sync-envelope replay service. * @param {object} args - Constructor arguments. * @param {import("./device-identity.js").SyncJsonWebKey} args.backendPublicKey - Backend public key used to verify device certificates. * @param {Array} [args.offlineGrantSigningKeys] - Offline-grant verification keys. * @param {(userId: string) => Promise<{id: () => string} | null>} [args.actorLookup] - Optional lookup from grant user id to an actor object with an `id()` method. Defaults to a wrapper around the grant user id. * @param {(args: {actor: ReturnType, configuration: import("../configuration.js").default | null, grant: import("./offline-grant.js").OfflineGrant}) => Promise | import("../authorization/ability.js").default | null} [args.abilityFactory] - Builds the actor/grant-scoped ability used to authorize routed resources. Required for routed signed replay; without it every routed sync fails closed. * @param {Record>} [args.rest] - Remaining arguments forwarded to {@link SyncEnvelopeReplayService}. */ constructor({ abilityFactory, backendPublicKey, offlineGrantSigningKeys, actorLookup, ...rest }: { backendPublicKey: import("./device-identity.js").SyncJsonWebKey; offlineGrantSigningKeys?: Array; actorLookup?: (userId: string) => Promise<{ id: () => string; } | null>; abilityFactory?: (args: { actor: ReturnType; configuration: import("../configuration.js").default | null; grant: import("./offline-grant.js").OfflineGrant; }) => Promise | import("../authorization/ability.js").default | null; rest?: Record>; }); /** * Verifies signed mutations and then runs the generic replay loop over the * derived sync envelopes. Verified actor, grant, and derived syncs are kept * in the request-local `requestState` object so concurrent replay calls on * one service instance cannot cross their authentication state. * @param {Record>} params - Request params. * @param {Record>} [requestState] - Request-local state shared with the base replay hooks. * @returns {Promise<{syncs: Array>>}>} Replay response. */ replay(params: Record>, requestState?: Record>): Promise<{ syncs: Array>>; }>; /** * Returns the verified actor prepared during {@link SignedSyncEnvelopeReplayService#replay}. * @param {Record>} _params - Request params. * @param {Record>} requestState - Request-local state. * @returns {Promise<{authenticated: true, actor: ReturnType} | {authenticated: false, errorCode: string, errorMessage: string}>} Auth result. */ authenticateReplay(_params: Record>, requestState: Record>): Promise<{ authenticated: true; actor: ReturnType; } | { authenticated: false; errorCode: string; errorMessage: string; }>; /** * Returns the sync envelopes derived from the verified signed mutations. * @param {Record>} _params - Request params. * @param {Record>} requestState - Request-local state. * @returns {Array>>} Sync envelopes. */ replaySyncs(_params: Record>, requestState: Record>): Array>>; /** * Builds the replay context carrying the verified signed actor and grant * (with its scopes) plus the offline runtime marker, so resource hooks and * ability factories authorize against the signer instead of the uploader. * @param {{actor: ReturnType, params: Record>, requestState: Record>}} args - Actor, request params, and request-local state. * @returns {Promise>>} Replay context. */ buildReplayContext({ actor, requestState }: { actor: ReturnType; params: Record>; requestState: Record>; }): Promise>>; /** * Derives the routed-resource ability from the verified signed actor and * grant through the configured `abilityFactory`. The constructor-wide * uploader ability is never used for signed replay: without a factory (or a * factory result) every routed sync fails closed with a client-safe error. * @param {{actor: ReturnType, context: Record>}} args - Verified actor and replay context. * @returns {Promise<{ability: import("../authorization/ability.js").default, abilityContext: Record>}>} Scoped ability and resource context. */ replayAbilityFor({ actor, context }: { actor: ReturnType; context: Record>; }): Promise<{ ability: import("../authorization/ability.js").default; abilityContext: Record>; }>; /** * Verifies every signed mutation, its offline grant, and the actor/grant * consistency, then transforms the envelopes into the sync format the base * replay service understands. * @param {Record>} params - Request params. * @returns {Promise<{actor: ReturnType, grant: import("./offline-grant.js").OfflineGrant, syncs: Array>>}>} Verified actor, common grant, and derived syncs. */ verifyAndTransformSignedReplay(params: Record>): Promise<{ actor: ReturnType; grant: import("./offline-grant.js").OfflineGrant; syncs: Array>>; }>; /** * Validates a mutation against the current sync manifest: the model and the * operation must be enabled, and the mutation's policy hash must match the * current manifest policy hash. * @param {{mutation: import("./device-identity.js").SyncMutation, syncManifest: Record}} args - Validation args. * @returns {void} Throws a client-safe error when the current policy denies the mutation. */ validateCurrentSyncPolicy({ mutation, syncManifest }: { mutation: import("./device-identity.js").SyncMutation; syncManifest: Record; }): void; /** * Validates the verified offline grant against the current sync policy, the * same contract as the controller's sync replay endpoint: the grant resource * entry must be a normalized manifest entry (enabled with an operations list * and the current policy hash), it must list the mutation operation, and its * policy hash must equal both the mutation and current manifest hashes. * Legacy array/true grant-resource shortcuts are not the bootstrap contract * and never authorize a mutation. * @param {{mutation: import("./device-identity.js").SyncMutation, offlineGrant: import("./offline-grant.js").OfflineGrant, syncManifest: Record}} args - Validation args. * @returns {void} Throws a client-safe error when the grant does not authorize the mutation. */ validateGrantAgainstSyncPolicy({ mutation, offlineGrant, syncManifest }: { mutation: import("./device-identity.js").SyncMutation; offlineGrant: import("./offline-grant.js").OfflineGrant; syncManifest: Record; }): void; /** * Transforms a verified signed mutation into a generic sync envelope. * @param {{mutation: import("./device-identity.js").SyncMutation}} args - Transform args. * @returns {Record>} Sync envelope. */ syncFromSignedMutation({ mutation }: { mutation: import("./device-identity.js").SyncMutation; }): Record>; } //# sourceMappingURL=signed-sync-envelope-replay-service.d.ts.map