From 67afeb82bc84fd86e6c2c54baed50b13ed6b3c7c Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Tue, 21 Jul 2026 12:29:38 +1000 Subject: [PATCH] UID2-7505: Point v2 envelope error responses to encryption/decryption docs The v2 request-envelope parse errors ("Version mismatch", "Body too short", "Check encryption key") gave callers no hint that the body must be an encrypted request envelope rather than base64-encoded plain JSON. Private Operator customers hitting the endpoint with a raw curl call (base64 of JSON) repeatedly got the opaque "Invalid body: Version mismatch." and mistook it for a server-side bug. Append a hint to these three envelope errors explaining that the body must be encrypted and linking to the encryption/decryption code examples. The leading machine-readable phrase is preserved so any downstream pattern-matching is unaffected. Co-Authored-By: Claude Opus 4.8 Ticket: UID2-7505 Branch: syw-UID2-7505-descriptive-envelope-errors --- .../uid2/operator/service/V2RequestUtil.java | 14 +++++++++++--- .../com/uid2/operator/V2RequestUtilTest.java | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/uid2/operator/service/V2RequestUtil.java b/src/main/java/com/uid2/operator/service/V2RequestUtil.java index c19fc70db..19c45ca56 100644 --- a/src/main/java/com/uid2/operator/service/V2RequestUtil.java +++ b/src/main/java/com/uid2/operator/service/V2RequestUtil.java @@ -51,6 +51,14 @@ public boolean isValid() { private static final byte VERSION = 1; + // Appended to request-envelope parse errors to help callers who send an unencrypted body (e.g. a raw + // curl request with base64-encoded JSON) understand that the body must be an encrypted request envelope. + static final String ENCRYPTION_HINT = " The request body must be an encrypted request envelope, not plain or" + + " base64-encoded JSON. If you are calling this endpoint directly (for example with curl), base64-encoding" + + " the JSON is not enough. See" + + " https://unifiedid.com/docs/getting-started/gs-encryption-decryption#encryption-and-decryption-code-examples" + + " for payload encryption and response decryption code examples."; + public static final int V2_REFRESH_PAYLOAD_LENGTH = 388; public static final long V2_REQUEST_TIMESTAMP_DRIFT_THRESHOLD_IN_MINUTES = 1; @@ -119,18 +127,18 @@ private static V2Request parseRequestCommon(byte[] bodyBytes, ClientKey ck, IClo } if (bodyBytes.length < MIN_PAYLOAD_LENGTH) { - return new V2Request("Invalid body: Body too short. Check encryption method."); + return new V2Request("Invalid body: Body too short. Check encryption method." + ENCRYPTION_HINT); } if (bodyBytes[0] != VERSION) { - return new V2Request("Invalid body: Version mismatch."); + return new V2Request("Invalid body: Version mismatch." + ENCRYPTION_HINT); } byte[] decryptedBody; try { decryptedBody = AesGcm.decrypt(bodyBytes, 1, ck.getSecretBytes()); } catch (Exception ex) { - return new V2Request("Invalid body: Check encryption key (ClientSecret)"); + return new V2Request("Invalid body: Check encryption key (ClientSecret)" + ENCRYPTION_HINT); } // Request envelop format: diff --git a/src/test/java/com/uid2/operator/V2RequestUtilTest.java b/src/test/java/com/uid2/operator/V2RequestUtilTest.java index d08b52389..71b6c2a86 100644 --- a/src/test/java/com/uid2/operator/V2RequestUtilTest.java +++ b/src/test/java/com/uid2/operator/V2RequestUtilTest.java @@ -120,7 +120,24 @@ public void testParseRequestWithTooShortBody() { V2RequestUtil.V2Request res = V2RequestUtil.parseRequestAsString("dGVzdA==", null, clock); - assertEquals("Invalid body: Body too short. Check encryption method.", res.errorMessage); + assertThat(res.errorMessage).startsWith("Invalid body: Body too short. Check encryption method."); + assertThat(res.errorMessage).contains("gs-encryption-decryption"); + } + + @Test + public void testParseRequestWithVersionMismatch() { + when(clock.now()).thenReturn(MOCK_NOW); + + // Base64 of plain (unencrypted) JSON long enough to pass the length check but whose first byte + // is not the expected envelope version byte - this is what a raw curl-with-base64 request produces. + String plainJsonBase64 = java.util.Base64.getEncoder().encodeToString( + "{\"email_hash\":\"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=\",\"version\":2}" + .getBytes(java.nio.charset.StandardCharsets.UTF_8)); + + V2RequestUtil.V2Request res = V2RequestUtil.parseRequestAsString(plainJsonBase64, null, clock); + + assertThat(res.errorMessage).startsWith("Invalid body: Version mismatch."); + assertThat(res.errorMessage).contains("gs-encryption-decryption"); } @Test