Activation and deactivation ids are UUIDs, not integers - #28
Merged
Merged
Conversation
parse_activation() only read `id` when the JSON value was a number, but the API issues UUID primary keys, so the field always arrives as a string. The id stayed 0, and client.cpp's `activation.id() <= 0` identity check then rejected every successful activation with "Activation response identity is inconsistent". activate() was therefore broken against production for every C++ consumer. The tests missed it because they only ever fed parse_activation an integer id, which is a shape the server does not send. Reported by a customer who read the source and diagnosed it correctly himself. Activation::id() is now a std::string, matching the Swift SDK, which already types this field as String. Integer ids are still accepted and normalised so older servers keep working.
fc0d088 fixed parse_activation but left three things behind: 1. tests/test_license.cpp and tests/scenario_test.cpp still built an Activation from an integer id and called std::to_string on id(). With -Werror the test target did not compile, so CI on the branch would have been red and the fix could never have merged as-is. 2. parse_deactivation had the identical bug: it read activation_id only when the JSON value was a number, the server sends the activation's UUID, so the field stayed 0 and client.cpp's check rejected every successful deactivation with "Deactivation response is incomplete". Worse than activate: the throw happens before the local cache is cleared, so the seat was released on the server while the client kept its license, offline token and machine file. Deactivation::activation_id is now a std::string; integers are still accepted and normalised. 3. The Unreal integration parses both responses on its own and required a numeric id in both places. It now accepts a string or a legacy integer. Tests updated to feed the UUID shape the server actually sends. 382/382 pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTtmNBLK1ivBapaS3sXysu
…ned key is raw base64 The signing_public_key docblock promised the key would be fetched from the API on first use. That is true for activate() and checkout_machine_file() and false for verify_machine_file() and the offline restore path, which are the offline entry points and deliberately never touch the network. A customer read the promise, fetched his machine file with curl, called verify_machine_file() and got "Public key required for machine file verification" (licenseseat-cpp#27). The README's Manual Storage example showed that exact flow with no key and no prior checkout, and the pinning example showed a PEM/DER-looking key that is_valid_ed25519_public_key silently rejects. The server returns the raw 32-byte key base64-encoded; the docs now say so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTtmNBLK1ivBapaS3sXysu
…e project version to 0.6.0 fc0d088 was cut before the v0.6.1 tag, so merging it as-is would have set CMakeLists VERSION back from 0.6.1 to 0.6.0. Activation::id() and Deactivation::activation_id changing type is a public API change, so this is 0.7.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BTtmNBLK1ivBapaS3sXysu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
activate()anddeactivate()have been broken against production for every C++ consumer since the SDK was written. Reported independently by two customers: Dave Carkeet (Aug 24, with a debugger trace) and Michael Rieger / FreeD Printing GmbH (Sep 3), both seeingFailed to parse response: Activation response identity is inconsistenton an activation the dashboard shows as successful.Root cause. The server issues UUID primary keys, so
idandactivation_idarrive as strings.parse_activationandparse_deactivationonly read them when the JSON value was a number, the field stayed0, and the identity/completeness checks inclient.cpprejected every real response. The API's own OpenAPI annotations declared these fieldsInteger(fixed in rameerez/license_seat#27), which is where the assumption came from. The tables were created with UUID keys on day one; there was never an integer to parse.Three commits:
fc0d088Activation::id()becomesstd::string; integers still accepted and normalised.83f8afbThe same fix forDeactivation::activation_id. This one was worse: the throw happened before the local cache was cleared, so the server released the seat while the client kept its license, offline token and machine file. Also fixes the Unreal integration, which parses both responses on its own, and two test files that still built anActivationfrom an integer (the branch did not compile with-Werrorbefore this).d6d5859Docs:signing_public_keyis never fetched on the offline path (verify_machine_file()and the cold-start restore), so pin it; the README's Manual Storage example showed exactly the unsupported flow the second customer followed, and its pinning example showed a PEM/DER key thatis_valid_ed25519_public_keysilently rejects. The server returns the raw 32-byte key base64-encoded.Verification.
ctest: 382/382 pass (main: the test target did not build).main,Activate licensefails with the customers' exact error; on this branch, activate, validate-with-activation, deactivate, and both async paths pass with UUID ids.Public key required for machine file verification;fetch_signing_key(kid)+ explicit key verifies; pinnedsigning_public_keyin a fresh client verifies with the license payload present; a wrong fingerprint failsdecryption_failed.API change.
Activation::id()andDeactivation::activation_idarestd::string. Anything storing them as integers changes one line. Suggest tagging v0.7.0.Not fixed here (filed separately): cold-start offline restore fails unless
signing_public_keyis pinned, because the fetched key is only cached in memory;MachineFilehas nokey_idfield so the explicit-key path needscrypto::internal::extract_machine_file_key_id.🤖 Generated with Claude Code
https://claude.ai/code/session_01BTtmNBLK1ivBapaS3sXysu