@eventsdk/sdk 0.3.1 → 0.4.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/conformance/event-link/harness.d.ts +13 -0
- package/dist/conformance/event-link/run-suite.d.ts +2 -0
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.js +1 -1
- package/dist/contracts/generated/event-link-authorization-request.d.ts +22 -0
- package/dist/contracts/generated/event-link-proposal.d.ts +27 -0
- package/dist/contracts/generated/event-link-token-request.d.ts +7 -0
- package/dist/contracts/generated/portable-event-link-authorization-request.d.ts +98 -0
- package/dist/contracts/generated/portable-event-link-proposal.d.ts +121 -0
- package/dist/contracts/generated/portable-event-link-token-request.d.ts +34 -0
- package/dist/contracts/index.d.ts +8 -3
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/validation/event-link.d.ts +9 -0
- package/dist/event-link/errors.d.ts +5 -0
- package/dist/event-link/exchange.d.ts +9 -0
- package/dist/event-link/index.d.ts +12 -0
- package/dist/event-link/index.js +1 -0
- package/dist/event-link/primitives.d.ts +10 -0
- package/dist/event-link/protocol.d.ts +21 -0
- package/dist/event-link/receiver-ports.d.ts +42 -0
- package/dist/event-link/receiver.d.ts +18 -0
- package/dist/event-link/registration.d.ts +21 -0
- package/dist/event-link/sender.d.ts +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +5 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EventLinkAuthorizationRequest } from '../generated/event-link-authorization-request.js';
|
|
2
|
+
import type { EventLinkTokenRequest } from '../generated/event-link-token-request.js';
|
|
3
|
+
import type { EventLinkProposal } from '../generated/event-link-proposal.js';
|
|
4
|
+
export declare const validateEventLinkAuthorizationRequest: (value: unknown) => import("./contract-error.js").ValidationResult<EventLinkAuthorizationRequest>;
|
|
5
|
+
export declare const validateEventLinkTokenRequest: (value: unknown) => import("./contract-error.js").ValidationResult<EventLinkTokenRequest>;
|
|
6
|
+
export declare const validateEventLinkProposal: (value: unknown) => import("./contract-error.js").ValidationResult<EventLinkProposal>;
|
|
7
|
+
export declare const parseEventLinkAuthorizationRequest: (value: unknown) => EventLinkAuthorizationRequest;
|
|
8
|
+
export declare const parseEventLinkTokenRequest: (value: unknown) => EventLinkTokenRequest;
|
|
9
|
+
export declare const parseEventLinkProposal: (value: unknown) => EventLinkProposal;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare class EventLinkProtocolError extends Error {
|
|
2
|
+
readonly code: 'INVALID' | 'FORBIDDEN' | 'EXPIRED' | 'CHANGED' | 'CONFLICT' | 'CODE_INVALID' | 'UNKNOWN';
|
|
3
|
+
constructor(code: 'INVALID' | 'FORBIDDEN' | 'EXPIRED' | 'CHANGED' | 'CONFLICT' | 'CODE_INVALID' | 'UNKNOWN');
|
|
4
|
+
}
|
|
5
|
+
export declare function eventLinkInput<T>(parse: () => T): T;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type EventLinkProposal } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import { type EventLinkSenderBinding } from './protocol.js';
|
|
3
|
+
export interface EventLinkExchangeOptions {
|
|
4
|
+
readonly fetch?: typeof fetch;
|
|
5
|
+
readonly now?: () => number;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function exchangeEventLinkCode(binding: EventLinkSenderBinding, code: string, options?: EventLinkExchangeOptions): Promise<EventLinkProposal>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { EventLinkProtocolError } from './errors.js';
|
|
2
|
+
export { EVENT_LINK_MAX_BYTES, createEventLinkVerifier, createEventLinkState, eventLinkChallenge, eventLinkHash, } from './primitives.js';
|
|
3
|
+
export { assertEventLinkRegistration, eventLinkRegistrationRevision } from './registration.js';
|
|
4
|
+
export type { EventLinkClientRegistration, EventLinkReceiverRegistration } from './registration.js';
|
|
5
|
+
export { buildEventLinkAuthorizationUrl, parseEventLinkAuthorization, parseEventLinkToken, validateEventLinkCallback, validateEventLinkProposalBinding, } from './protocol.js';
|
|
6
|
+
export type { EventLinkSenderBinding } from './protocol.js';
|
|
7
|
+
export { exchangeEventLinkCode } from './exchange.js';
|
|
8
|
+
export type { EventLinkExchangeOptions } from './exchange.js';
|
|
9
|
+
export { planEventLinkCallback, continueEventLinkAuthorization } from './sender.js';
|
|
10
|
+
export type { EventLinkSenderSnapshot, EventLinkSenderClaim, EventLinkSenderPort, } from './sender.js';
|
|
11
|
+
export { EventLinkReceiver } from './receiver.js';
|
|
12
|
+
export type { EventLinkConsent, EventLinkReceiverActor, EventLinkReceiverState, EventLinkReceiverRecord, EventLinkReceiverTransaction, EventLinkReceiverPort, } from './receiver-ports.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var EventLinkProtocolError=class extends Error{constructor(code){super("EVENT_LINK_"+code);this.code=code;this.name="EventLinkProtocolError"}code};function eventLinkInput(parse){try{return parse()}catch{throw new EventLinkProtocolError("INVALID")}}import{createHash,randomBytes}from"node:crypto";var EVENT_LINK_MAX_BYTES=8192;var eventLinkHash=value=>createHash("sha256").update(value).digest("hex");var createEventLinkVerifier=()=>randomBytes(48).toString("base64url");var createEventLinkState=()=>randomBytes(32).toString("base64url");function eventLinkChallenge(verifier){if(!/^[A-Za-z0-9._~-]{43,128}$/.test(verifier))throw new EventLinkProtocolError("INVALID");return createHash("sha256").update(verifier).digest("base64url")}function eventLinkUrl(value,allowLoopbackHttp=false){const url=new URL(value);const loopback=["localhost","127.0.0.1","[::1]"].includes(url.hostname);if(url.username||url.password||url.hash||url.href!==value||url.protocol!=="https:"&&!(allowLoopbackHttp&&loopback&&url.protocol==="http:"))throw new EventLinkProtocolError("INVALID");return url}function eventLinkOrigin(value,allowLoopbackHttp=false){const url=eventLinkUrl(value+"/",allowLoopbackHttp);if(url.origin!==value)throw new EventLinkProtocolError("INVALID");return url}function eventLinkCallbackUrl(value,allowLoopbackHttp=false){const url=eventLinkUrl(value,allowLoopbackHttp);if(["code","error","state","iss"].some(key=>url.searchParams.has(key)))throw new EventLinkProtocolError("INVALID");return url}function eventLinkFields(params,fields){if(Buffer.byteLength(params.toString())>EVENT_LINK_MAX_BYTES)throw new EventLinkProtocolError("INVALID");const result=Object.create(null);for(const[key,value]of params){if(!fields.includes(key)||Object.hasOwn(result,key))throw new EventLinkProtocolError("INVALID");result[key]=value}return result}function eventLinkSize(value){if(Buffer.byteLength(JSON.stringify(value))>EVENT_LINK_MAX_BYTES)throw new EventLinkProtocolError("INVALID")}import{canonicalJson,parseExchangeRegistration}from"@eventsdk/sdk/contracts";function assertEventLinkRegistration(request,config){eventLinkInput(()=>{eventLinkOrigin(config.origin,config.allowLoopbackHttp);eventLinkCallbackUrl(request.redirect_uri,config.allowLoopbackHttp);const matches=config.clients.filter(client2=>client2.clientId===request.client_id&&client2.redirectUri===request.redirect_uri);if(matches.length!==1)throw new Error("registration");const client=matches[0];if(!client||canonicalJson(client.source)!==canonicalJson(request.source)||!client.eventTypes.includes(request.event_type))throw new Error("binding");parseExchangeRegistration({schemaVersion:1,requestId:request.request_id,expectedRevision:null,source:request.source,target:{serviceRef:config.serviceRef,tenantRef:"registration-validation"},grant:{grantRef:request.grant_ref,version:1,eventType:request.event_type,consumerRef:config.consumerRef},status:"ACTIVE"})})}var eventLinkRegistrationRevision=config=>eventLinkHash(canonicalJson(config));function eventLinkRedirect(request,origin,result){const url=new URL(request.redirect_uri);url.searchParams.set("state",request.state);url.searchParams.set("iss",origin);for(const[key,value]of Object.entries(result))url.searchParams.set(key,value);return url.href}import{canonicalJson as canonicalJson2,parseEventLinkAuthorizationRequest,parseEventLinkTokenRequest,parseEventLinkProposal,parseJsonStrict}from"@eventsdk/sdk/contracts";import{deriveExchangeConnectionRef}from"@eventsdk/sdk/core";import{createHash as createHash2}from"node:crypto";var authorizationFields=["profile","response_type","client_id","redirect_uri","scope","state","code_challenge","code_challenge_method","request_id","source","event_type","grant_ref","grant_version"];var tokenFields=["grant_type","client_id","redirect_uri","code","code_verifier"];function parseEventLinkAuthorization(params,registration){return eventLinkInput(()=>{const fields=eventLinkFields(params,authorizationFields);const request=parseEventLinkAuthorizationRequest({...fields,source:parseJsonStrict(fields.source??"")});assertEventLinkRegistration(request,registration);return request})}function parseEventLinkToken(params){return eventLinkInput(()=>{const value=params instanceof URLSearchParams?eventLinkFields(params,tokenFields):params;eventLinkSize(value);return parseEventLinkTokenRequest(value)})}function buildEventLinkAuthorizationUrl(binding){return eventLinkInput(()=>{eventLinkOrigin(binding.origin,binding.allowLoopbackHttp);eventLinkCallbackUrl(binding.redirectUri,binding.allowLoopbackHttp);const request=parseEventLinkAuthorizationRequest({profile:"event-link/1",response_type:"code",client_id:binding.clientId,redirect_uri:binding.redirectUri,scope:"eventsdk.connection.authorize",state:binding.state,code_challenge:eventLinkChallenge(binding.codeVerifier),code_challenge_method:"S256",request_id:binding.requestId,source:binding.source,event_type:binding.eventType,grant_ref:binding.grantRef,grant_version:"1"});const params=new URLSearchParams({...request,source:JSON.stringify(request.source)});eventLinkFields(params,authorizationFields);return new URL("/eventsdk/connect/authorize?"+params,binding.origin).href})}function validateEventLinkCallback(params,binding){const fields=eventLinkFields(params,["code","error","state","iss"]);if(Object.values(fields).some(value=>value.length>512)||fields.state!==binding.state||fields.iss!==binding.origin||Object.hasOwn(fields,"code")===Object.hasOwn(fields,"error"))throw new EventLinkProtocolError("INVALID");if(fields.error!==void 0){if(fields.error!=="access_denied")throw new EventLinkProtocolError("INVALID");return null}if(!fields.code||!/^[A-Za-z0-9_-]{32,256}$/.test(fields.code))throw new EventLinkProtocolError("INVALID");return fields.code}async function validateEventLinkProposalBinding(raw,binding,now=Date.now()){if(!Number.isSafeInteger(binding.expiresAt)||!Number.isSafeInteger(now))throw new EventLinkProtocolError("INVALID");const proposal=eventLinkInput(()=>{eventLinkSize(raw);return parseEventLinkProposal(raw)});const expected=await deriveExchangeConnectionRef({source:binding.source.source,from:{serviceRef:binding.source.serviceRef,tenantRef:binding.source.tenantRef},to:proposal.target},{sha256:async value=>createHash2("sha256").update(value).digest("hex")});if(proposal.requestId!==binding.requestId||canonicalJson2(proposal.source)!==canonicalJson2(binding.source)||proposal.target.serviceRef!==binding.targetServiceRef||proposal.grant.eventType!==binding.eventType||proposal.grant.grantRef!==binding.grantRef||Date.parse(proposal.expiresAt)<=now||Date.parse(proposal.expiresAt)>binding.expiresAt||proposal.connectionRef!==expected)throw new EventLinkProtocolError("UNKNOWN");return proposal}import{parseJsonStrict as parseJsonStrict2}from"@eventsdk/sdk/contracts";import{boundedOperation}from"@eventsdk/sdk/core";async function readProposal(response,signal){if(!response.ok||!/^application\/json(?:\s*;|$)/i.test(response.headers.get("content-type")??"")||!response.body){void response.body?.cancel().catch(()=>{});throw new EventLinkProtocolError("UNKNOWN")}const reader=response.body.getReader(),chunks=[];const cancel=()=>{void reader.cancel().catch(()=>{})};signal.addEventListener("abort",cancel,{once:true});let size=0;try{signal.throwIfAborted();while(true){const part=await reader.read();signal.throwIfAborted();if(part.done)break;size+=part.value.length;if(size>EVENT_LINK_MAX_BYTES)throw new EventLinkProtocolError("UNKNOWN");chunks.push(part.value)}return parseJsonStrict2(new TextDecoder("utf-8",{fatal:true}).decode(Buffer.concat(chunks)))}finally{signal.removeEventListener("abort",cancel);if(!signal.aborted)cancel();reader.releaseLock()}}async function exchangeEventLinkCode(binding,code,options={}){const timeoutMs=options.timeoutMs??15e3;if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>15e3)throw new EventLinkProtocolError("INVALID");try{buildEventLinkAuthorizationUrl(binding);const token=parseEventLinkToken({grant_type:"authorization_code",client_id:binding.clientId,redirect_uri:binding.redirectUri,code,code_verifier:binding.codeVerifier});return await boundedOperation({timeoutMs,...options.signal?{signal:options.signal}:{}},async signal=>{const response=await(options.fetch??fetch)(new URL("/eventsdk/connect/token",binding.origin),{method:"POST",credentials:"omit",redirect:"error",signal,headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({...token})});if(signal.aborted){void response.body?.cancel().catch(()=>{});signal.throwIfAborted()}return validateEventLinkProposalBinding(await readProposal(response,signal),binding,(options.now??Date.now)())})}catch{throw new EventLinkProtocolError("UNKNOWN")}}import{canonicalJson as canonicalJson3}from"@eventsdk/sdk/contracts";function planEventLinkCallback(current,params,now=Date.now()){if(!Number.isSafeInteger(current.binding.expiresAt)||!Number.isSafeInteger(now))throw new EventLinkProtocolError("INVALID");if(current.phase!=="LOGIN"||current.binding.expiresAt<=now)throw new EventLinkProtocolError("CHANGED");const code=validateEventLinkCallback(params,current.binding);return{snapshot:structuredClone(current),code,phase:code===null?"DENIED":"EXCHANGING"}}async function assertCompletion(current,claim,proposal,now){if(current.phase!=="EXCHANGING"||current.binding.expiresAt<=now||canonicalJson3(current.binding)!==canonicalJson3(claim.snapshot.binding))throw new EventLinkProtocolError("CHANGED");await validateEventLinkProposalBinding(proposal,current.binding,now)}async function continueEventLinkAuthorization(port,params,options={}){const now=options.now??Date.now;const claim=await port.claim(current=>planEventLinkCallback(current,params,now()));if(claim.code===null)return{phase:"DENIED",claim};let proposal;try{proposal=await exchangeEventLinkCode(claim.snapshot.binding,claim.code,options)}catch{await port.unknown(claim);return{phase:"UNKNOWN",claim}}try{await port.confirm(claim,proposal,current=>assertCompletion(current,claim,proposal,now()))}catch(error){await port.unknown(claim);throw error}return{phase:"CONFIRM",claim}}import{randomUUID as randomUUID2}from"node:crypto";import{canonicalJson as canonicalJson4,parseEventLinkProposal as parseEventLinkProposal2}from"@eventsdk/sdk/contracts";import{deriveExchangeConnectionRef as deriveExchangeConnectionRef2}from"@eventsdk/sdk/core";import{createHash as createHash3,randomUUID}from"node:crypto";function sameEventLinkActor(a,b){return a!==null&&["actorRef","tenantRef","sessionBinding","authorityVersion"].every(key=>a[key]===b[key])}var eventLinkConsentBinding=(ref,actor)=>eventLinkHash(canonicalJson4([ref,actor]));function eventLinkPending(value,proof,config,now){if(!value||!/^[A-Za-z0-9_-]{43}$/.test(proof)||value.proofHash!==eventLinkHash(proof))throw new EventLinkProtocolError("FORBIDDEN");if(value.expiresAt<=now)throw new EventLinkProtocolError("EXPIRED");if(value.registrationRevision!==eventLinkRegistrationRevision(config))throw new EventLinkProtocolError("CHANGED");assertEventLinkRegistration(value.request,config);return value}async function createEventLinkProposal(value,config,actor,now){const source=value.request.source,target={serviceRef:config.serviceRef,tenantRef:actor.tenantRef};const connectionRef=await deriveExchangeConnectionRef2({source:source.source,from:{serviceRef:source.serviceRef,tenantRef:source.tenantRef},to:target},{sha256:async input=>createHash3("sha256").update(input).digest("hex")});const proposal=parseEventLinkProposal2({schemaVersion:1,requestId:value.request.request_id,source,target,grant:{grantRef:value.request.grant_ref,version:1,eventType:value.request.event_type,consumerRef:config.consumerRef},connectionRef,targetDisplayName:[...actor.tenantDisplayName.trim()].slice(0,160).join(""),approvalRef:"urn:eventsdk:approval:"+randomUUID(),expiresAt:new Date(Math.min(value.expiresAt,now+6e4)).toISOString()});eventLinkSize(proposal);return proposal}var EventLinkReceiver=class{constructor(port,registration,now=Date.now){this.port=port;this.registration=registration;this.now=now}port;registration;now;async begin(params){const config=structuredClone(this.registration()),request=parseEventLinkAuthorization(params,config);const proof=createEventLinkState();const pending={ref:randomUUID2(),proofHash:eventLinkHash(proof),registrationRevision:eventLinkRegistrationRevision(config),request,expiresAt:this.now()+24e4,actor:null,codeHash:null,codeExpiresAt:null,proposal:null,state:"PENDING"};await this.port.transact(async tx=>{if(await tx.requestBySource(request.client_id,request.request_id))throw new EventLinkProtocolError("CONFLICT");await tx.insertRequest(pending)});return{ref:pending.ref,proof}}async consent(ref,proof,actorInput){const actor=structuredClone(actorInput);return this.port.transact(async tx=>{await tx.assertActor(actor);const value=eventLinkPending(await tx.request(ref),proof,this.registration(),this.now());if(value.actor&&!sameEventLinkActor(value.actor,actor))throw new EventLinkProtocolError("CHANGED");if(value.state!=="PENDING")throw new EventLinkProtocolError("CONFLICT");await tx.updateRequest({...value,actor:structuredClone(actor)},"PENDING");await tx.assertActor(actor);eventLinkPending(value,proof,this.registration(),this.now());return{ref,source:value.request.source,eventType:value.request.event_type,expiresAt:value.expiresAt,actor,consentBinding:eventLinkConsentBinding(ref,actor)}})}async decide(ref,proof,actorInput,consentBinding,decision){if(decision!=="allow"&&decision!=="deny")throw new EventLinkProtocolError("INVALID");const actor=structuredClone(actorInput);const config=structuredClone(this.registration()),code=createEventLinkVerifier();return this.port.transact(async tx=>{await tx.assertActor(actor);const value=eventLinkPending(await tx.request(ref),proof,config,this.now());if(value.state!=="PENDING"||!sameEventLinkActor(value.actor,actor)||consentBinding!==eventLinkConsentBinding(ref,actor))throw new EventLinkProtocolError("CHANGED");if(decision==="deny"){await tx.updateRequest({...value,state:"DENIED"},"PENDING");await tx.assertActor(actor);eventLinkPending(value,proof,this.registration(),this.now());return{location:eventLinkRedirect(value.request,config.origin,{error:"access_denied"})}}const proposal=await createEventLinkProposal(value,config,actor,this.now());await tx.approveGrant(proposal,actor,value.request.client_id);await tx.updateRequest({...value,actor:structuredClone(actor),proposal,codeHash:eventLinkHash(code),codeExpiresAt:Date.parse(proposal.expiresAt),state:"APPROVED"},"PENDING");await tx.assertActor(actor);eventLinkPending(value,proof,this.registration(),this.now());if(Date.parse(proposal.expiresAt)<=this.now())throw new EventLinkProtocolError("EXPIRED");return{location:eventLinkRedirect(value.request,config.origin,{code})}})}async exchange(raw){const token=structuredClone(parseEventLinkToken(raw)),config=structuredClone(this.registration());return this.port.transact(async tx=>{const value=await tx.requestByCode(eventLinkHash(token.code));if(!value||value.state!=="APPROVED"||!value.actor||!value.proposal||!value.codeExpiresAt||value.codeExpiresAt<=this.now()||value.expiresAt<=this.now()||value.registrationRevision!==eventLinkRegistrationRevision(config)||token.client_id!==value.request.client_id||token.redirect_uri!==value.request.redirect_uri||eventLinkChallenge(token.code_verifier)!==value.request.code_challenge)throw new EventLinkProtocolError("CODE_INVALID");assertEventLinkRegistration(value.request,config);await tx.assertActor(value.actor);await tx.assertGrant(value.proposal,value.actor);await tx.updateRequest({...value,state:"CONSUMED"},"APPROVED");await tx.assertActor(value.actor);if(value.registrationRevision!==eventLinkRegistrationRevision(this.registration())||value.codeExpiresAt<=this.now())throw new EventLinkProtocolError("CODE_INVALID");return value.proposal})}};export{EVENT_LINK_MAX_BYTES,EventLinkProtocolError,EventLinkReceiver,assertEventLinkRegistration,buildEventLinkAuthorizationUrl,continueEventLinkAuthorization,createEventLinkState,createEventLinkVerifier,eventLinkChallenge,eventLinkHash,eventLinkRegistrationRevision,exchangeEventLinkCode,parseEventLinkAuthorization,parseEventLinkToken,planEventLinkCallback,validateEventLinkCallback,validateEventLinkProposalBinding};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const EVENT_LINK_MAX_BYTES = 8192;
|
|
2
|
+
export declare const eventLinkHash: (value: string) => string;
|
|
3
|
+
export declare const createEventLinkVerifier: () => string;
|
|
4
|
+
export declare const createEventLinkState: () => string;
|
|
5
|
+
export declare function eventLinkChallenge(verifier: string): string;
|
|
6
|
+
export declare function eventLinkUrl(value: string, allowLoopbackHttp?: boolean): URL;
|
|
7
|
+
export declare function eventLinkOrigin(value: string, allowLoopbackHttp?: boolean): URL;
|
|
8
|
+
export declare function eventLinkCallbackUrl(value: string, allowLoopbackHttp?: boolean): URL;
|
|
9
|
+
export declare function eventLinkFields(params: URLSearchParams, fields: readonly string[]): Record<string, string>;
|
|
10
|
+
export declare function eventLinkSize(value: unknown): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type EventLinkAuthorizationRequest, type EventLinkTokenRequest, type EventLinkProposal, type ExchangeRegistration } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import { type EventLinkReceiverRegistration } from './registration.js';
|
|
3
|
+
export declare function parseEventLinkAuthorization(params: URLSearchParams, registration: EventLinkReceiverRegistration): EventLinkAuthorizationRequest;
|
|
4
|
+
export declare function parseEventLinkToken(params: URLSearchParams | unknown): EventLinkTokenRequest;
|
|
5
|
+
export interface EventLinkSenderBinding {
|
|
6
|
+
readonly requestId: string;
|
|
7
|
+
readonly source: ExchangeRegistration['source'];
|
|
8
|
+
readonly targetServiceRef: string;
|
|
9
|
+
readonly origin: string;
|
|
10
|
+
readonly clientId: string;
|
|
11
|
+
readonly redirectUri: string;
|
|
12
|
+
readonly state: string;
|
|
13
|
+
readonly codeVerifier: string;
|
|
14
|
+
readonly eventType: string;
|
|
15
|
+
readonly grantRef: string;
|
|
16
|
+
readonly expiresAt: number;
|
|
17
|
+
readonly allowLoopbackHttp?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function buildEventLinkAuthorizationUrl(binding: EventLinkSenderBinding): string;
|
|
20
|
+
export declare function validateEventLinkCallback(params: URLSearchParams, binding: Pick<EventLinkSenderBinding, 'state' | 'origin'>): string | null;
|
|
21
|
+
export declare function validateEventLinkProposalBinding(raw: unknown, binding: EventLinkSenderBinding, now?: number): Promise<EventLinkProposal>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EventLinkAuthorizationRequest, EventLinkProposal } from '@eventsdk/sdk/contracts';
|
|
2
|
+
export interface EventLinkReceiverActor {
|
|
3
|
+
readonly actorRef: string;
|
|
4
|
+
readonly tenantRef: string;
|
|
5
|
+
readonly sessionBinding: string;
|
|
6
|
+
readonly authorityVersion: string;
|
|
7
|
+
readonly tenantDisplayName: string;
|
|
8
|
+
}
|
|
9
|
+
export type EventLinkReceiverState = 'PENDING' | 'DENIED' | 'APPROVED' | 'CONSUMED';
|
|
10
|
+
export interface EventLinkReceiverRecord {
|
|
11
|
+
readonly ref: string;
|
|
12
|
+
readonly proofHash: string;
|
|
13
|
+
readonly registrationRevision: string;
|
|
14
|
+
readonly request: EventLinkAuthorizationRequest;
|
|
15
|
+
readonly expiresAt: number;
|
|
16
|
+
readonly actor: EventLinkReceiverActor | null;
|
|
17
|
+
readonly codeHash: string | null;
|
|
18
|
+
readonly codeExpiresAt: number | null;
|
|
19
|
+
readonly proposal: EventLinkProposal | null;
|
|
20
|
+
readonly state: EventLinkReceiverState;
|
|
21
|
+
}
|
|
22
|
+
export interface EventLinkConsent {
|
|
23
|
+
readonly ref: string;
|
|
24
|
+
readonly source: EventLinkAuthorizationRequest['source'];
|
|
25
|
+
readonly eventType: string;
|
|
26
|
+
readonly expiresAt: number;
|
|
27
|
+
readonly actor: EventLinkReceiverActor;
|
|
28
|
+
readonly consentBinding: string;
|
|
29
|
+
}
|
|
30
|
+
export interface EventLinkReceiverTransaction {
|
|
31
|
+
assertActor(actor: EventLinkReceiverActor): Promise<void>;
|
|
32
|
+
request(ref: string): Promise<EventLinkReceiverRecord | null>;
|
|
33
|
+
requestBySource(clientId: string, requestId: string): Promise<EventLinkReceiverRecord | null>;
|
|
34
|
+
requestByCode(codeHash: string): Promise<EventLinkReceiverRecord | null>;
|
|
35
|
+
insertRequest(record: EventLinkReceiverRecord): Promise<void>;
|
|
36
|
+
updateRequest(record: EventLinkReceiverRecord, expectedState: EventLinkReceiverState): Promise<void>;
|
|
37
|
+
approveGrant(proposal: EventLinkProposal, actor: EventLinkReceiverActor, clientId: string): Promise<void>;
|
|
38
|
+
assertGrant(proposal: EventLinkProposal, actor: EventLinkReceiverActor): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export interface EventLinkReceiverPort {
|
|
41
|
+
transact<T>(operation: (tx: EventLinkReceiverTransaction) => Promise<T>): Promise<T>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EventLinkProposal } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import { type EventLinkReceiverRegistration } from './registration.js';
|
|
3
|
+
import type { EventLinkConsent, EventLinkReceiverActor, EventLinkReceiverPort } from './receiver-ports.js';
|
|
4
|
+
export declare class EventLinkReceiver {
|
|
5
|
+
private readonly port;
|
|
6
|
+
private readonly registration;
|
|
7
|
+
private readonly now;
|
|
8
|
+
constructor(port: EventLinkReceiverPort, registration: () => EventLinkReceiverRegistration, now?: () => number);
|
|
9
|
+
begin(params: URLSearchParams): Promise<{
|
|
10
|
+
ref: string;
|
|
11
|
+
proof: string;
|
|
12
|
+
}>;
|
|
13
|
+
consent(ref: string, proof: string, actorInput: EventLinkReceiverActor): Promise<EventLinkConsent>;
|
|
14
|
+
decide(ref: string, proof: string, actorInput: EventLinkReceiverActor, consentBinding: string, decision: 'allow' | 'deny'): Promise<{
|
|
15
|
+
location: string;
|
|
16
|
+
}>;
|
|
17
|
+
exchange(raw: unknown): Promise<EventLinkProposal>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ExchangeRegistration, type EventLinkAuthorizationRequest } from '@eventsdk/sdk/contracts';
|
|
2
|
+
export interface EventLinkClientRegistration {
|
|
3
|
+
readonly clientId: string;
|
|
4
|
+
readonly redirectUri: string;
|
|
5
|
+
readonly source: ExchangeRegistration['source'];
|
|
6
|
+
readonly eventTypes: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface EventLinkReceiverRegistration {
|
|
9
|
+
readonly origin: string;
|
|
10
|
+
readonly serviceRef: string;
|
|
11
|
+
readonly consumerRef: string;
|
|
12
|
+
readonly clients: readonly EventLinkClientRegistration[];
|
|
13
|
+
readonly allowLoopbackHttp?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function assertEventLinkRegistration(request: EventLinkAuthorizationRequest, config: EventLinkReceiverRegistration): void;
|
|
16
|
+
export declare const eventLinkRegistrationRevision: (config: EventLinkReceiverRegistration) => string;
|
|
17
|
+
export declare function eventLinkRedirect(request: EventLinkAuthorizationRequest, origin: string, result: {
|
|
18
|
+
code: string;
|
|
19
|
+
} | {
|
|
20
|
+
error: 'access_denied';
|
|
21
|
+
}): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type EventLinkProposal } from '@eventsdk/sdk/contracts';
|
|
2
|
+
import { type EventLinkExchangeOptions } from './exchange.js';
|
|
3
|
+
import { type EventLinkSenderBinding } from './protocol.js';
|
|
4
|
+
export interface EventLinkSenderSnapshot {
|
|
5
|
+
readonly binding: EventLinkSenderBinding;
|
|
6
|
+
readonly phase: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EventLinkSenderClaim {
|
|
9
|
+
readonly snapshot: EventLinkSenderSnapshot;
|
|
10
|
+
readonly code: string | null;
|
|
11
|
+
readonly phase: 'DENIED' | 'EXCHANGING';
|
|
12
|
+
}
|
|
13
|
+
export interface EventLinkSenderPort<T extends EventLinkSenderClaim> {
|
|
14
|
+
claim(plan: (current: EventLinkSenderSnapshot) => EventLinkSenderClaim): Promise<T>;
|
|
15
|
+
confirm(claim: T, proposal: EventLinkProposal, guard: (current: EventLinkSenderSnapshot) => Promise<void>): Promise<void>;
|
|
16
|
+
unknown(claim: T): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare function planEventLinkCallback(current: EventLinkSenderSnapshot, params: URLSearchParams, now?: number): EventLinkSenderClaim;
|
|
19
|
+
export declare function continueEventLinkAuthorization<T extends EventLinkSenderClaim>(port: EventLinkSenderPort<T>, params: URLSearchParams, options?: EventLinkExchangeOptions): Promise<{
|
|
20
|
+
phase: 'DENIED' | 'CONFIRM' | 'UNKNOWN';
|
|
21
|
+
claim: T;
|
|
22
|
+
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -18,5 +18,7 @@ export * from './node-config/index.js';
|
|
|
18
18
|
export * as nodeConfig from './node-config/index.js';
|
|
19
19
|
export * from './node-worker/index.js';
|
|
20
20
|
export * as nodeWorker from './node-worker/index.js';
|
|
21
|
+
export * from './event-link/index.js';
|
|
22
|
+
export * as eventLink from './event-link/index.js';
|
|
21
23
|
export type { ResultReadRequest } from './core/index.js';
|
|
22
24
|
export type { ResultReadRequest as IdentityResultReadRequest } from './identity-contracts/index.js';
|
package/dist/index.js
CHANGED
|
@@ -18,3 +18,5 @@ export * from './node-config/index.js';
|
|
|
18
18
|
export * as nodeConfig from './node-config/index.js';
|
|
19
19
|
export * from './node-worker/index.js';
|
|
20
20
|
export * as nodeWorker from './node-worker/index.js';
|
|
21
|
+
export * from './event-link/index.js';
|
|
22
|
+
export * as eventLink from './event-link/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventsdk/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"./node-worker": {
|
|
52
52
|
"types": "./dist/node-worker/index.d.ts",
|
|
53
53
|
"import": "./dist/node-worker/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./event-link": {
|
|
56
|
+
"types": "./dist/event-link/index.d.ts",
|
|
57
|
+
"import": "./dist/event-link/index.js"
|
|
54
58
|
}
|
|
55
59
|
},
|
|
56
60
|
"bin": {
|