Repository navigation
connect-library 5.0.0, attachment-receiver 5.0.0, connect-e2ee 5.0.0
Major version. connect-e2ee now reports a sender's keys being unavailable as their own verification outcome, instead of a SENDER_KEY refusal (tsanetgit/Connect_SDK#147). That adds a case to a sealed type, which breaks exhaustive switches at source. TsaNetApiSession becomes AutoCloseable, and the deprecated CaseNoteDto and CollaborationRequestStatusDto constructors are removed (tsanetgit/Connect_SDK#139). PinnedJwksPartnerKeys waits a minute after a failed fetch before fetching that company's keys again (tsanetgit/Connect_SDK#173), and a refused OAuth token call names its error code (tsanetgit/Connect_SDK#45). A token request that fails without being refused is now its own ConnectApiException, and PinnedJwksPartnerKeys is now AutoCloseable. The release also redacts credentials, tokens and partner-written text from the toString() of SDK records, and refuses JWKS URLs that have no host (tsanetgit/Connect_SDK#162). connect-library makes its mapping from the generated OpenAPI models to its records public, as OpenApiRecords (tsanetgit/Connect_SDK#146). attachment-receiver has no changes of its own; it moves to 5.0.0 with the others. Runtime unchanged: Spring Boot 4.1, Jackson 3, JDK 21.
The upgrade guide is the 5.0.0 entry in skills/connect-sdk-deploy/references/consume-artifact.md. Coming from 3.0.0 or earlier, apply the earlier entries first.
New in 5.0.0
-
Verification.KeysUnavailable(#166).E2eeProfile.verifyCaseandverifyNotereturn it whenPartnerKeysthrowsIOExceptionfetching the sending company's keys, either at first or on the refetch for an unknownkid.- Retry, don't refuse: it carries no value and the record wasn't judged. It comes before the replay check, so no
jtiis used up, and verifying the same record again later is checked from scratch, including asFIRST_RECEIPT. - Back off, and bound the retries: after a failed fetch,
PinnedJwksPartnerKeysdoesn't fetch that company's keys again for a minute. A retry inside the minute returnsKeysUnavailableagain without contacting the partner, and keys can stay unavailable for up to a minute after the last failed fetch, even once its endpoint has recovered. A set it holds that hasn't expired still answers for akidin that set, and akidthat isn't there reads asKeysUnavailableduring the wait. A timeout or a non-200 answer can clear on its own. ButPinnedJwksPartnerKeysalso throws for a company with no configured JWKS URL, and for a set that is too large, unparseable or holds a private key, which a retry won't fix until the configuration or the partner's set changes. Cap retries and alert when they run out. - Every other
SENDER_KEYrefusal is unchanged.
- Retry, don't refuse: it carries no value and the record wasn't judged. It comes before the replay check, so no
-
Sensitive values are redacted in diagnostics (#165).
- SDK records print
<redacted>intoString()for passwords, client and webhook secrets, and authentication tokens. - Case, note and request tokens count as sensitive, because they work as capabilities, not just IDs. They are redacted too, including in webhook raw payloads.
- Attachment parameters print their names only.
CaseNoteDto,CaseResponseDto,CollaborationRequestStatusDtoandStoredAttachmentForwardResultDtoleave partner-written text and personal details out oftoString().- Constructors and accessors are unchanged. JSON serialization and explicit accessor calls are not redacted.
- SDK records print
-
OpenApiRecords, the public mapping from generated models to records (#168, #169). A connector that calls the generated client directly can build the recordsconnect-e2eetakes with the same code the facades use, instead of keeping its own copy. A copy that drops a value a manifest covers gets the record refused atFIELDS.collaborationRequest(CollaborationRequestStatusDTO)returns aCollaborationRequestStatusDto.caseNote(CaseNoteDTO, caseToken)returns aCaseNoteDto. The generated note carries no case token, so pass the token of the case the note was read under;verifyNotecompares it with the case's.formField(FieldMetadataDTO)returns aFormFieldDto.- The parameters are generated types, so a rename in the Connect OpenAPI spec changes these public signatures.
-
Sessions can be closed (#179).
TsaNetApiSessionisAutoCloseable. Each session holds a JDKHttpClient, with its selector thread and pooled connections, from the moment it opens, and until now nothing released them. Close a session you drop, for example on a credential change.- It doesn't wait: requests already sent run to completion. A later one fails the way an unreachable API does:
ConnectApiExceptionof kindCONNECTIVITY, orAttachmentV2Exceptionclient/connectivityon the V2 facades. Asendstill running can't complete its grant, which stays open until it expires. AccountScopedTsaNetApiSessiongoes back to having no session until the next login, and a login or authenticate as a different account closes the previous account's session.- Each V2 upload runs on a client of its own, stopped when it ends, so uploads no longer share pooled connections.
- A Spring
@Beanthat returns a session is now closed when its context shuts down.
- It doesn't wait: requests already sent run to completion. A later one fails the way an unreachable API does:
-
PinnedJwksPartnerKeysis nowAutoCloseable(#181). Close an instance you drop: a fetch already sent finishes, a set it holds still answers for akidin that set, and a lookup that would fetch reads asKeysUnavailable.
Breaking changes in 5.0.0
-
A switch over
Verificationmust handleKeysUnavailable.Verificationis sealed, and now permitsVerified,RefusedandKeysUnavailable. An exhaustiveswitchwithout the new case stops compiling. A consumer that bumps without recompiling links, but throwsMatchExceptionat the first unavailable-keys read. Add the case and retry the record, with a limit:switch (e2ee.verifyCase(record, receipt)) { case Verified<DecryptedCase> ok -> handle(ok.content()); case Refused<DecryptedCase> no -> audit(no.step(), no.field(), no.reason()); case KeysUnavailable<DecryptedCase> later -> retryLater(record); // a bounded number of times }
-
A
defaultarm or aninstanceofchain overVerificationstill compiles, andKeysUnavailablelands in that branch, or throwsClassCastExceptionon a cast. Check those too, and treatKeysUnavailableas retry-with-a-limit, never as refused or verified. -
TsaNetApiSessiongains an abstractclose(). Code that implements it must add one. -
The deprecated record constructors are gone:
CaseNoteDto's 14- and 18-argument forms andCollaborationRequestStatusDto's 10-, 11- and 13-argument forms. Call the canonical constructor (20 and 16 arguments), passingnullfor a value you don't have.
Also check in 5.0.0
- Refusal steps that move. Step 3's party checks now run before the key lookup: the record naming no receiving party, and this member being neither its
issnor itsaud. A record whose parties don't include this member is refused atCLAIMS, where it used to be refused atSENDER_KEY(wrongkid) orSIGNATURE. Anything that audits or alerts bystepwill see that shift. The reason strings are unchanged. - Unavailable keys aren't refusals any more. Code that treated a
SENDER_KEYrefusal with the reason "the sending company's public keys are unavailable" as retryable, by matching the string, should switch onKeysUnavailableinstead. That refusal is no longer produced. PinnedJwksPartnerKeysrefuses a host-less URL when it's constructed. It throwsIllegalArgumentException("A JWKS URL must have a host"). Before, such a URL failed only when it was fetched, as an unchecked exception. This covers every entry in the map, including companies you never read from.toString()output changes on the records above. Code that parsedtoString(), or that relied on a log line carrying a token, a note's text or a case's description, sees<redacted>or no field at all.- Messages without tokens. The SDK's own exception and sync messages no longer contain request tokens.
WebhookInboundServicemasks the request token in a failed sync's message. Rows inwebhook_inbound_eventwritten by 4.0.0 keep their old messages. - A refused OAuth token call names its error code. The
IllegalStateExceptionmessage isOAuth client-credentials request failed: HTTP <status> (<code>)when the token endpoint answers with a known OAuth error code, for exampleinvalid_client. Otherwise it stays... HTTP <status>. Entra'serror_descriptionstill isn't included, because it names the app. Code that matched the old message exactly should match its prefix. - Tokens and parse errors (#181). A token response whose access token isn't all visible ASCII now fails sign-in with a fixed message, and a malformed token response, an unusable token URL or a webhook callback URL that isn't a URI no longer has its value quoted in an exception. A token request that fails without being refused is now
ConnectApiExceptionof kindOTHER, status 0, titleToken request: an unusable token URL or API base URL at sign-in, a token or login response the client can't read, a malformed one, a 5xx, 408 or 429 from the token endpoint, or a login response with no access token. Those wereIllegalArgumentException,RestClientExceptionorIllegalStateException, so a 4.0.0catchstops matching, and an expired session that meets one no longer reads as a 401Session expired. A refusal by the token endpoint staysIllegalStateException, and a 401Session expiredon renewal of an expired token. - A note with no priority is refused with
A note priority is required(#181). - Malformed partner input no longer throws. A manifest or protected value that is the JSON
null, or a recipientkidthat isn't a string, is now aRefused. A JWKS endpoint serving such a body or a malformed HTTP response is nowKeysUnavailable. All of these used to escapeverifyCaseandverifyNoteas unchecked exceptions.
Known limits in 5.0.0
- A key rotation can still refuse a valid case.
PinnedJwksPartnerKeysrefetches a company's keys at most once a minute. Within that window, a manifest naming a newkidsees the cached set, and the case is refused atSENDER_KEY. - Server text is passed through. A
ConnectApiExceptioncarries the API's own error text, which can echo a token, for example "Case with token ... not found." TSANet-integration-appstill prints tokens where an operator asks for them, because later commands take them as arguments.- A slow endpoint can block a company's keys for a minute. A fetch cancelled after running for 2 seconds or more starts the wait for every caller of that instance, for that company.
- A very short budget isn't throttled. A fetch interrupted before it has run for 2 seconds doesn't start the minute's wait, so a caller with a budget under 2 seconds on an endpoint that hangs still makes a request on each lookup.
demo-ui
- Cross-site requests are refused (#177). The Basic gate now uses CSRF tokens, and a cross-site form gets a 403.
- Request errors keep their own status (#178). A malformed request answers 400, 404, 405 or 415 instead of 502, and the SDK's validation messages answer 400 with their reason. Anything unexpected still answers 502, now with a fixed message.
- Upload filenames (#180). The V1 upload route uses the same filename handling as V2, and a malformed multipart answers 400.
- A request is refused before its body is read when it isn't signed in (#182).
- A guest login has a lower upload limit (#182): 1 GB by default, set with
TSANET_DEMO_AUTH_GUEST_MAX_UPLOAD_BYTES. A guest'sPOST,PUTorPATCHmust declare aContent-Length, and a guest can't send chunked. The operator keeps 20 GB.