[vesync] Add auth V2 support#20236
Conversation
[veSync] Initial upload of prototype POC code before cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Non-op cleanup's from checks Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Constant cleanups Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Refactored to remove inner class with cleanups Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Bugfix previous constants update Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Cleanup commented out lines Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Cleanups - removal of unit test and cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] SCA cleanups Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Token refresh support Signed-off-by: David Goodyear <david.goodyear@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds VeSync “Auth V2” login support (incl. region-aware endpoint selection) to restore/maintain connectivity for newer VeSync accounts and data centers, alongside some minor SCA-driven cleanup.
Changes:
- Introduce Auth V2 login handshake (token request → authorize-code login → optional region redirect) and persist the selected server base URL in the user session.
- Route subsequent API calls via the selected regional server URL and add a token-refresh retry on token-related errors.
- Minor robustness/SCA updates (Locale-stable
toLowerCase, timer calculation usingInstant, hashmap dispose behavior).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| bundles/org.openhab.binding.vesync/src/main/test/org/openhab/binding/vesync/internal/handler/requests/VesyncAuthenticatedRequestTest.java | Renames the public test class (currently breaks Java filename/classname contract). |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/handlers/VeSyncDeviceAirPurifierHandler.java | Locale-stable lowercasing; removes unnecessary suppression. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/handlers/VeSyncDeviceAirHumidifierHandler.java | Locale-stable lowercasing; switches auto-off time calc to Instant. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/handlers/VeSyncBridgeHandler.java | Adds token-expiry detection and re-login retry wrapper for authorized calls. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/handlers/VeSyncBaseDeviceHandler.java | Switches request URL building to relative paths (base URL now comes from session/server selection). |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/responses/VeSyncUserSession.java | Adds serverUrl to session; adjusts session fields/toString. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/responses/VeSyncAuthTokenResponse.java | New DTO for the auth-token step response. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/responses/VeSyncAuthLoginWithAuthorizeCodeVeSyncResponse.java | New DTO for the authorize-code login response (incl. region redirect data). |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncRequest.java | Refactors to extend new base request DTO (currently introduces duplicate httpMethod field). |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncProtocolConstants.java | Adds US/EU server addresses; converts endpoints to relative paths. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncBaseRequest.java | New common request base (acceptLanguage/traceId/method/httpMethod). |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncAuthTokenRequest.java | New request DTO for the auth-token step. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncAuthLoginWithAuthorizeCodeVeSyncRegionChange.java | New DTO for region-change authorize-code login. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/dto/requests/VeSyncAuthLoginWithAuthorizeCodeVeSync.java | New DTO for authorize-code login. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/api/VeSyncV2ApiHelper.java | Replaces legacy login with Auth V2 helper; prefixes requests with the session’s selected server URL; fixes dispose map handling. |
| bundles/org.openhab.binding.vesync/src/main/java/org/openhab/binding/vesync/internal/api/LoginAuthV2Helper.java | New helper implementing the Auth V2 sequence + cross-region redirect handling (needs null-safety hardening). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
[veSync] Copilot suggestion minor mods Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] LoginAuthV2Helper.java NPE Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Refactored to remove inner class with cleanups Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Token refresh correction and NPE handling updates based on co-pilot feedback Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Further NPE bits from copilot flagged bits Signed-off-by: David Goodyear <david.goodyear@gmail.com>
|
Hi @psmedley I worked through each of the bits co-pilot raised. I resolved so I could track but the changes are from commit 4e9da4f onwards, along with one bug I spotted while re-reading. Currently soak testing here on our main server now - but would be good to open up for human review when there is time please. |
I'm not a reviewer (my java is nowhere near good enough), I was just trying to help the you and the ultimate approver by kicking off the co-pilot reviews, and as they're a quick/easy win to address. |
psmedley
left a comment
There was a problem hiding this comment.
Added a few things I noticed
|
Hi @psmedley - thank you for looking through - I didn't know whether you had to do something to get it assigned to someone or mark co-pilot as good / re-run it, etc.... So big thanks for having a look through - I guess this gets automatically picked up then when someone has time and assigns it to themselves irrelevant of the co-pilot run then? |
Yeah a reviewer will pick it up when they have time, I have a couple of PRs for Ring to extend functionality which are waiting. I added the 'Bug' tag to this one as the vesync binding is currently broken for many users - so this one should hopefully get looked at first. Bug fixes make it into 5.1.x builds, enhancements only go into 5.2.x (due midyear) |
[veSync] Const creation Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Removal of unnecessary static imports Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Alignment to use const Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Const simplification Signed-off-by: David Goodyear <david.goodyear@gmail.com>
|
@openhab/add-ons-maintainers any chance of a review on this change please - currently users need to use snapshot builds, until we get it merged, for any new accounts at least. (I've been running locally now on our home server for approx. 2 month's - perfectly stable and running BAU). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| final ContentResponse response = request.send(); | ||
|
|
||
| final VeSyncAuthLoginWithAuthorizeCodeVeSyncResponse result = VeSyncConstants.GSON | ||
| .fromJson(response.getContentAsString(), VeSyncAuthLoginWithAuthorizeCodeVeSyncResponse.class); | ||
|
|
||
| if (result == null) { | ||
| return false; | ||
| } | ||
|
|
||
| if (result.isMsgSuccess()) { | ||
| loginData.token = result.result.token; | ||
| loginData.accountId = result.result.accountID; | ||
| return true; |
There was a problem hiding this comment.
In the regional-redirect login flow, the HTTP status isn’t validated and result.result is dereferenced without checking for null. A non-200 response or a response without result will cause incorrect behavior or an NPE. Check response.getStatus() first and ensure result.result is non-null before reading token/accountID (otherwise return false or throw AuthenticationException).
There was a problem hiding this comment.
Yeah thats a good catch. Added the missing http response code check required.
| if (session != null && !url.startsWith(session.getServerUrl())) { | ||
| url = session.getServerUrl() + url; |
There was a problem hiding this comment.
session.getServerUrl() may be null (it’s not annotated and is populated dynamically), but it’s used in url.startsWith(...) and string concatenation. This can throw an NPE during requests (e.g., device discovery). Guard against null/blank server URLs and only prefix when the incoming url is relative.
| if (session != null && !url.startsWith(session.getServerUrl())) { | |
| url = session.getServerUrl() + url; | |
| if (session != null) { | |
| String serverUrl = session.getServerUrl(); | |
| if (serverUrl != null && !serverUrl.isBlank() && !url.startsWith("http://") | |
| && !url.startsWith("https://") && !url.startsWith(serverUrl)) { | |
| url = serverUrl + url; | |
| } |
There was a problem hiding this comment.
Adjusted with additional null check, to protect against NPE.
| String result = api.reqV2Authorized(url, macId, requestData); | ||
|
|
||
| VeSyncResponse responseFrame = VeSyncConstants.GSON.fromJson(result, VeSyncResponse.class); | ||
|
|
There was a problem hiding this comment.
fromJson(result, VeSyncResponse.class) can throw a JsonSyntaxException if the backend returns non-JSON (HTML error pages, proxies, etc.). Right now that would bubble up as a runtime exception and break command handling. Wrap the parse in a try/catch and skip token-refresh logic when parsing fails (keeping the original result).
| @SerializedName("result") | ||
| public VeSyncAuthTokenResponse.LoginTokenResponse result; | ||
|
|
||
| public class LoginTokenResponse { |
There was a problem hiding this comment.
LoginTokenResponse is a non-static inner class. Gson cannot reliably instantiate non-static inner classes during deserialization, which can cause the auth-token response parsing to fail at runtime. Make LoginTokenResponse a static nested class (or a top-level class) so it can be deserialized safely.
| public class LoginTokenResponse { | |
| public static class LoginTokenResponse { |
There was a problem hiding this comment.
I was going to move all the inner classes out to top level dto. However on looking through the files as they currently are it would make the files much less readable when comparing to the raw payloads. This could be done on another PR but will possibly bloat this one. I have however changed the inner classes to be all static on the responses as recommended.
| logger.trace("Refreshing API token due to error response regarding the token"); | ||
| final VeSyncBridgeConfiguration config = getConfigAs(VeSyncBridgeConfiguration.class); | ||
| final String passwordMd5 = VeSyncV2ApiHelper.calculateMd5(config.password); | ||
| api.login(config.username, passwordMd5, "Europe/London"); |
There was a problem hiding this comment.
The zone is global? Or do you need to make this configurable?
There was a problem hiding this comment.
I don't believe the timezone matters, python vesync default to America/New_york - https://github.com/webdjoe/pyvesync/blob/2f2511f27acc8162722f2bf2eee5e74134d07cdd/README.md?plain=1#L57
There was a problem hiding this comment.
It is a parameter to the request, so it does something and i noticed different zones are used, so if you don;lt experience any issue, atleast align them.
There was a problem hiding this comment.
Ideally it would be driven from the system and passed as you say Isiepel. Your right psmedley regarding timezone it doesn't technically get used (at least on the api's mapped by pyvesync - I suspect the app uses it when generating accounts possibly is my guess). However the API's are undocumented and mapped from the mapping work from pyvesync. Therefore I wouldn't plug new data generally into it, as they do appear to validate requests against what they expect the app to possibly send. I tested the old America/New York tz against my primary account (appears to be USA based due to its age), and a test one I setup for testing this binding (which logs in to the eu servers) I can see redirect's to eu based server, and it accepts the America/New York tz so have aligned on that one. If times are ever mapped rather than durations then this may need to be looked at but then pyvesync will then also map true tz data alongside the new api's as well, so it can be updated then based on the region openhab is running in. I have therefore kept as is just aligned for consistency as suggested, in case of future use.
lsiepel
left a comment
There was a problem hiding this comment.
Beside the comment i made and copilot did. I wonder if the differnt (hardcoded and/or default) timezone/locale are right. As this supports both us and europ, i see both of them mixed. Please make sure that it works for both areas as intended.
|
Besides fixing the open comments, i wonder if you have any thoughts about backporting this to 4.3.x ? |
|
Yeah I was going to try and look over this long weekend at the merge once the changes are good. I’ll try to back port the auth updates. The comment it made about the inner classes I moved out as I thought that was the preference, but can move back to inner if needed. What’s the pref @Isiepel on that bit?On 3 Apr 2026, at 10:14, lsiepel ***@***.***> wrote:lsiepel left a comment (openhab/openhab-addons#20236)
Besides fixing the open comments, i wonder if you have any thoughts about backporting this to 4.3.x ?
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Stand-alone /top level dto is preferred. I'll look at refactoring this on a second pass after this is merged and backported. As I was about to do it, then realised it would create a lot more changes, that would introduce risk on what is a bug fix. It will need some careful renaming to keep everything easily traceable when done. So will move, but if okay I'll open a second less urgent PR for that against this binding. |
[veSync] Timezone use alignment Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Add V1 failsafe fallback Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Add V1 failsafe fallback Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Revert and adjust V1 fallback behaviour Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] Further inner class static updates on responses Signed-off-by: David Goodyear <david.goodyear@gmail.com>
[veSync] HTTP Response code check Signed-off-by: David Goodyear <david.goodyear@gmail.com>
* [veSync] Initial upload of prototype POC code before cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com>
…20236) * [veSync] Initial upload of prototype POC code before cleanup Signed-off-by: David Goodyear <david.goodyear@gmail.com>
…20236) [veSync] Auth V2 Support so users can use the binding again (openhab#20236) Signed-off-by: David Goodyear <david.goodyear@gmail.com>
The changes are currently being soak tested but the new flows have been proven by multiple users in different data centre's in the linked issue thread below. (Soak testing primarily for token refresh if it does happen). Opening now for review while the soak test's run.
The main adjustments here relate to: #19127
This adjusts:
Corrects a error when dispose is called, as .clear on the hashmap now appears to cause errors, it is re-referenced to a new HashMap instance to ensure a empty one is referenced without errors.
Authorization handling with VeSync servers to support a newer scheme, that at some point new accounts could only use. Old accounts do actually appear to support the new schema on testing with my account, so the upgrade should be backwards compatible for V2 based devices at least.
The URL/s utilised so the relevant cloud service is targeted based on the data extracted from the authorization handshakes.
A few other minor adjustments for Static Code Analysis bits that were flagged, since the last time this binding was updated, to remove them.