homebridge-tsvesync 1.0.95 → 1.0.97

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.
@@ -0,0 +1,770 @@
1
+ #!/bin/bash
2
+
3
+ # VeSync Authentication Type Testing Script
4
+ # Consolidated version - tests both NEW and LEGACY authentication flows
5
+ # Supports regional endpoints, PII redaction, and comprehensive error reporting
6
+ #
7
+ # Usage: ./vesync-auth-test.sh [-v] [-d] [email] [password]
8
+ # ./vesync-auth-test.sh --help
9
+ #
10
+ # Environment variables: VESYNC_USERNAME, VESYNC_PASSWORD
11
+
12
+ set -eo pipefail
13
+
14
+ # Script constants
15
+ SCRIPT_VERSION="2.1.0"
16
+ SCRIPT_NAME="VeSync Authentication Type Detector"
17
+ DEFAULT_TIMEOUT=15
18
+
19
+ # Colors and icons
20
+ RED='\033[0;31m'
21
+ GREEN='\033[0;32m'
22
+ YELLOW='\033[1;33m'
23
+ BLUE='\033[0;34m'
24
+ CYAN='\033[0;36m'
25
+ BOLD='\033[1m'
26
+ NC='\033[0m'
27
+
28
+ SUCCESS="✓"
29
+ FAILURE="✗"
30
+ WARNING="⚠"
31
+ INFO="ℹ"
32
+ ARROW="→"
33
+
34
+ # Global variables
35
+ VERBOSE=false
36
+ DEBUG=false
37
+ EMAIL=""
38
+ PASSWORD=""
39
+ APP_ID=""
40
+ TERMINAL_ID=""
41
+ DEBUG_DIR=""
42
+
43
+ # Function to show usage
44
+ show_usage() {
45
+ printf '%s\n' "${BOLD}${SCRIPT_NAME} v${SCRIPT_VERSION}${NC}"
46
+ printf '\n'
47
+ printf '%s\n' "${BOLD}USAGE:${NC}"
48
+ printf ' %s [OPTIONS] [EMAIL] [PASSWORD]\n' "$0"
49
+ printf '\n'
50
+ printf '%s\n' "${BOLD}OPTIONS:${NC}"
51
+ printf ' -v, --verbose Enable verbose output (show detailed API responses)\n'
52
+ printf ' -d, --debug Enable debug mode (save API responses to files)\n'
53
+ printf ' -h, --help Show this help message\n'
54
+ printf '\n'
55
+ printf '%s\n' "${BOLD}ARGUMENTS:${NC}"
56
+ printf ' EMAIL VeSync account email address\n'
57
+ printf ' PASSWORD VeSync account password\n'
58
+ printf '\n'
59
+ printf '%s\n' "${BOLD}ENVIRONMENT VARIABLES:${NC}"
60
+ printf ' VESYNC_USERNAME VeSync account email (alternative to EMAIL argument)\n'
61
+ printf ' VESYNC_PASSWORD VeSync account password (alternative to PASSWORD argument)\n'
62
+ printf '\n'
63
+ printf '%s\n' "${BOLD}EXAMPLES:${NC}"
64
+ printf ' %s user@example.com mypassword\n' "$0"
65
+ printf ' %s -v user@example.com mypassword\n' "$0"
66
+ printf ' VESYNC_USERNAME=user@example.com VESYNC_PASSWORD=mypassword %s\n' "$0"
67
+ }
68
+
69
+ # Function to log messages
70
+ log() {
71
+ local level=$1
72
+ local message=$2
73
+ local timestamp=$(date '+%H:%M:%S')
74
+
75
+ case $level in
76
+ "ERROR") echo -e "${RED}[${timestamp}] ERROR: ${message}${NC}" >&2 ;;
77
+ "WARN") echo -e "${YELLOW}[${timestamp}] WARN: ${message}${NC}" ;;
78
+ "INFO") echo -e "${BLUE}[${timestamp}] INFO: ${message}${NC}" ;;
79
+ "SUCCESS") echo -e "${GREEN}[${timestamp}] SUCCESS: ${message}${NC}" ;;
80
+ "DEBUG") [[ "$VERBOSE" == "true" ]] && echo -e "${CYAN}[${timestamp}] DEBUG: ${message}${NC}" ;;
81
+ esac
82
+ }
83
+
84
+ # Function to redact PII
85
+ redact_pii() {
86
+ local input="$1"
87
+ local type="${2:-email}"
88
+
89
+ case $type in
90
+ "email")
91
+ if [[ "$input" =~ ^[^@]+@(.+)$ ]]; then
92
+ echo "***@${BASH_REMATCH[1]}"
93
+ else
94
+ echo "***"
95
+ fi
96
+ ;;
97
+ "token"|"password")
98
+ local len=${#input}
99
+ if [[ $len -gt 8 ]]; then
100
+ echo "${input:0:4}...${input: -4}"
101
+ elif [[ $len -gt 0 ]]; then
102
+ echo "***"
103
+ else
104
+ echo ""
105
+ fi
106
+ ;;
107
+ "account_id")
108
+ local len=${#input}
109
+ if [[ $len -gt 8 ]]; then
110
+ echo "${input:0:4}...${input: -4}"
111
+ else
112
+ echo "***"
113
+ fi
114
+ ;;
115
+ *)
116
+ echo "***"
117
+ ;;
118
+ esac
119
+ }
120
+
121
+ # Function to generate unique identifiers
122
+ generate_app_id() {
123
+ if command -v openssl >/dev/null 2>&1; then
124
+ openssl rand -hex 4
125
+ elif [[ -r /dev/urandom ]]; then
126
+ cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | fold -w 8 | head -n 1
127
+ else
128
+ echo "app$(date +%s | tail -c 5)"
129
+ fi
130
+ }
131
+
132
+ generate_terminal_id() {
133
+ if command -v openssl >/dev/null 2>&1; then
134
+ openssl rand -hex 8
135
+ elif [[ -r /dev/urandom ]]; then
136
+ cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | fold -w 16 | head -n 1
137
+ else
138
+ echo "terminal$(date +%s)"
139
+ fi
140
+ }
141
+
142
+ # Function to setup debug directory
143
+ setup_debug_dir() {
144
+ if [[ "$DEBUG" == "true" ]]; then
145
+ DEBUG_DIR="/tmp/vesync-auth-debug-$(date +%s)"
146
+ mkdir -p "$DEBUG_DIR"
147
+ log "DEBUG" "Debug directory created: $DEBUG_DIR"
148
+ fi
149
+ }
150
+
151
+ # Function to save response to debug file
152
+ save_debug_response() {
153
+ local response="$1"
154
+ local filename="$2"
155
+
156
+ if [[ "$DEBUG" == "true" && -n "$DEBUG_DIR" ]]; then
157
+ if command -v jq >/dev/null 2>&1; then
158
+ echo "$response" | jq '.' > "$DEBUG_DIR/$filename.json" 2>/dev/null || echo "$response" > "$DEBUG_DIR/$filename.txt"
159
+ else
160
+ echo "$response" > "$DEBUG_DIR/$filename.txt"
161
+ fi
162
+ log "DEBUG" "Response saved to: $DEBUG_DIR/$filename"
163
+ fi
164
+ }
165
+
166
+ # Function to hash password
167
+ hash_password() {
168
+ local password="$1"
169
+ if command -v md5 >/dev/null 2>&1; then
170
+ echo -n "$password" | md5 -q
171
+ elif command -v md5sum >/dev/null 2>&1; then
172
+ echo -n "$password" | md5sum | cut -d' ' -f1
173
+ else
174
+ log "ERROR" "No MD5 utility available (md5 or md5sum)"
175
+ exit 1
176
+ fi
177
+ }
178
+
179
+ # Function to extract JSON values (with fallback if jq not available)
180
+ extract_json_value() {
181
+ local json="$1"
182
+ local key="$2"
183
+
184
+ if command -v jq >/dev/null 2>&1; then
185
+ echo "$json" | jq -r ".$key" 2>/dev/null || echo ""
186
+ else
187
+ # Fallback using grep and cut
188
+ echo "$json" | grep -o "\"$key\":[^,}]*" | cut -d: -f2- | sed 's/^"//;s/"$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | head -n1
189
+ fi
190
+ }
191
+
192
+ # Function to get error code meaning
193
+ get_error_code_meaning() {
194
+ local code="$1"
195
+ case "$code" in
196
+ "-11000086") echo "Legacy authentication required" ;;
197
+ "-11012022") echo "App version too low / API version mismatch" ;;
198
+ "-11260022") echo "Cross-region authentication error" ;;
199
+ "-10011") echo "Account does not exist" ;;
200
+ "-10013") echo "Account locked" ;;
201
+ "-10014") echo "Incorrect password" ;;
202
+ "-10015") echo "Account not activated" ;;
203
+ "-11000020") echo "Invalid token" ;;
204
+ "-11000021") echo "Token expired" ;;
205
+ "-11000041") echo "Too many requests" ;;
206
+ *) echo "" ;;
207
+ esac
208
+ }
209
+
210
+ # Function to explain error codes
211
+ explain_error_code() {
212
+ local code="$1"
213
+ local message="$2"
214
+ local meaning=$(get_error_code_meaning "$code")
215
+
216
+ if [[ -n "$meaning" ]]; then
217
+ echo -e "${RED}${FAILURE}${NC} Error Code: ${BOLD}$code${NC}"
218
+ echo -e " Meaning: $meaning"
219
+ [[ -n "$message" ]] && echo -e " Message: $message"
220
+ else
221
+ echo -e "${RED}${FAILURE}${NC} Error Code: ${BOLD}$code${NC}"
222
+ [[ -n "$message" ]] && echo -e " Message: $message"
223
+ echo -e " ${YELLOW}${WARNING}${NC} Unknown error code - may indicate API changes"
224
+ fi
225
+ }
226
+
227
+ # Function to test network connectivity
228
+ test_connectivity() {
229
+ log "INFO" "Testing network connectivity..."
230
+
231
+ local endpoints=("smartapi.vesync.com" "smartapi.vesync.eu")
232
+ local connected=false
233
+
234
+ for endpoint in "${endpoints[@]}"; do
235
+ if curl -s --connect-timeout 5 --max-time 10 "https://$endpoint" >/dev/null 2>&1; then
236
+ log "SUCCESS" "Connected to $endpoint"
237
+ connected=true
238
+ else
239
+ log "WARN" "Cannot connect to $endpoint"
240
+ fi
241
+ done
242
+
243
+ if [[ "$connected" == "false" ]]; then
244
+ log "ERROR" "No connectivity to VeSync endpoints. Check your internet connection."
245
+ exit 1
246
+ fi
247
+ }
248
+
249
+ # Function to test NEW authentication
250
+ test_new_authentication() {
251
+ local base_url="$1"
252
+ local region="$2"
253
+
254
+ echo -e "\n${YELLOW}${BOLD}Testing NEW Authentication (PR #340)...${NC}"
255
+ echo -e "${CYAN}${ARROW} Endpoint: $base_url${NC}"
256
+ echo -e "${CYAN}${ARROW} Region: $region${NC}"
257
+
258
+ local hashed_password=$(hash_password "$PASSWORD")
259
+ local timestamp=$(date +%s)
260
+
261
+ # Build JSON payload as a single line (this approach works reliably)
262
+ local auth_payload="{\"email\":\"$EMAIL\",\"method\":\"authByPWDOrOTM\",\"password\":\"$hashed_password\",\"acceptLanguage\":\"en\",\"accountID\":\"\",\"authProtocolType\":\"generic\",\"clientInfo\":\"SM N9005\",\"clientType\":\"vesyncApp\",\"clientVersion\":\"VeSync 5.6.60\",\"debugMode\":false,\"osInfo\":\"Android\",\"terminalId\":\"$TERMINAL_ID\",\"timeZone\":\"America/New_York\",\"token\":\"\",\"userCountryCode\":\"$region\",\"appID\":\"$APP_ID\",\"sourceAppID\":\"$APP_ID\",\"traceId\":\"APP${APP_ID}${timestamp}\"}"
263
+
264
+ log "DEBUG" "Sending auth request to: $base_url/globalPlatform/api/accountAuth/v1/authByPWDOrOTM"
265
+
266
+ local auth_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
267
+ -X POST \
268
+ -H "Content-Type: application/json" \
269
+ -H "Accept: application/json" \
270
+ -H "User-Agent: VeSync/5.6.60 (Android; SM N9005)" \
271
+ -d "$auth_payload" \
272
+ "$base_url/globalPlatform/api/accountAuth/v1/authByPWDOrOTM" 2>/dev/null || echo '{"error":"network_error"}')
273
+
274
+ local region_lower=$(echo "$region" | tr '[:upper:]' '[:lower:]')
275
+ save_debug_response "$auth_response" "new_auth_${region_lower}_step1"
276
+
277
+ [[ "$VERBOSE" == "true" ]] && {
278
+ echo -e "\n${CYAN}Raw Response:${NC}"
279
+ if command -v jq >/dev/null 2>&1; then
280
+ echo "$auth_response" | jq '.'
281
+ else
282
+ echo "$auth_response"
283
+ fi
284
+ echo ""
285
+ }
286
+
287
+ # Check for network errors
288
+ if echo "$auth_response" | grep -q '"error":"network_error"' || [[ -z "$auth_response" ]]; then
289
+ echo -e "${RED}${FAILURE}${NC} Network error - cannot reach authentication endpoint"
290
+ return 1
291
+ fi
292
+
293
+ local response_code=$(extract_json_value "$auth_response" "code")
294
+ local response_msg=$(extract_json_value "$auth_response" "msg")
295
+ local authorize_code=$(extract_json_value "$auth_response" "result.authorizeCode")
296
+
297
+ log "DEBUG" "Response code: $response_code"
298
+ log "DEBUG" "Response message: $response_msg"
299
+
300
+ case "$response_code" in
301
+ "0")
302
+ if [[ -n "$authorize_code" && "$authorize_code" != "null" ]]; then
303
+ echo -e "${GREEN}${SUCCESS}${NC} NEW Authentication Step 1 successful!"
304
+ echo -e " ${ARROW} Authorize Code: $(redact_pii "$authorize_code" "token")"
305
+
306
+ # Step 2: Exchange authorize code for token
307
+ echo -e "\n${CYAN}Testing Step 2: Token exchange...${NC}"
308
+
309
+ local timestamp2=$(date +%s)
310
+ local biz_token=$(extract_json_value "$auth_response" "result.bizToken")
311
+ local token_payload="{\"method\":\"loginByAuthorizeCode4Vesync\",\"authorizeCode\":\"$authorize_code\",\"acceptLanguage\":\"en\",\"accountID\":\"\",\"clientInfo\":\"SM N9005\",\"clientType\":\"vesyncApp\",\"clientVersion\":\"VeSync 5.6.60\",\"debugMode\":false,\"emailSubscriptions\":false,\"osInfo\":\"Android\",\"terminalId\":\"$TERMINAL_ID\",\"timeZone\":\"America/New_York\",\"token\":\"\",\"bizToken\":\"$biz_token\",\"regionChange\":\"\",\"userCountryCode\":\"$region\",\"traceId\":\"APP${APP_ID}${timestamp2}\"}"
312
+
313
+ local token_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
314
+ -X POST \
315
+ -H "Content-Type: application/json" \
316
+ -H "Accept: application/json" \
317
+ -H "User-Agent: VeSync/5.6.60 (Android; SM N9005)" \
318
+ -d "$token_payload" \
319
+ "$base_url/user/api/accountManage/v1/loginByAuthorizeCode4Vesync" 2>/dev/null || echo '{"error":"network_error"}')
320
+
321
+ local region_lower2=$(echo "$region" | tr '[:upper:]' '[:lower:]')
322
+ save_debug_response "$token_response" "new_auth_${region_lower2}_step2"
323
+
324
+ local token_code=$(extract_json_value "$token_response" "code")
325
+ local token=$(extract_json_value "$token_response" "result.token")
326
+ local account_id=$(extract_json_value "$token_response" "result.accountID")
327
+ local country_code=$(extract_json_value "$token_response" "result.countryCode")
328
+
329
+ if [[ "$token_code" == "0" && -n "$token" && "$token" != "null" ]]; then
330
+ echo -e "${GREEN}${SUCCESS}${NC} NEW Authentication Step 2 successful!"
331
+ echo -e " ${ARROW} Token: $(redact_pii "$token" "token")"
332
+ echo -e " ${ARROW} Account ID: $(redact_pii "$account_id" "account_id")"
333
+ echo -e " ${ARROW} Country: ${country_code:-unknown}"
334
+ return 0
335
+ else
336
+ echo -e "${RED}${FAILURE}${NC} NEW Authentication Step 2 failed"
337
+ explain_error_code "$token_code" "$response_msg"
338
+ return 1
339
+ fi
340
+ else
341
+ echo -e "${RED}${FAILURE}${NC} NEW Authentication failed - no authorize code received"
342
+ return 1
343
+ fi
344
+ ;;
345
+ "-11000086")
346
+ echo -e "${BLUE}${INFO}${NC} Legacy authentication required"
347
+ echo -e " ${ARROW} This account uses the traditional login flow"
348
+ return 2 # Special return code for legacy auth required
349
+ ;;
350
+ "-11260022")
351
+ echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error"
352
+ echo -e " ${ARROW} Account may be registered in different region"
353
+ return 3 # Special return code for cross-region
354
+ ;;
355
+ *)
356
+ explain_error_code "$response_code" "$response_msg"
357
+ return 1
358
+ ;;
359
+ esac
360
+ }
361
+
362
+ # Function to test LEGACY authentication
363
+ test_legacy_authentication() {
364
+ local base_url="$1"
365
+ local region="$2"
366
+
367
+ echo -e "\n${YELLOW}${BOLD}Testing LEGACY Authentication...${NC}"
368
+ echo -e "${CYAN}${ARROW} Endpoint: $base_url${NC}"
369
+ echo -e "${CYAN}${ARROW} Region: $region${NC}"
370
+
371
+ local hashed_password=$(hash_password "$PASSWORD")
372
+ local timestamp=$(date +%s)
373
+
374
+ # Build legacy payload as single line
375
+ local legacy_payload="{\"email\":\"$EMAIL\",\"password\":\"$hashed_password\",\"appVersion\":\"2.8.6\",\"phoneBrand\":\"SM N9005\",\"phoneOS\":\"Android\",\"acceptLanguage\":\"en\",\"timeZone\":\"America/New_York\",\"method\":\"login\",\"token\":\"\",\"accountID\":\"\",\"devToken\":\"\",\"userType\":\"1\",\"traceId\":\"$timestamp\"}"
376
+
377
+ log "DEBUG" "Sending legacy auth request to: $base_url/cloud/v1/user/login"
378
+
379
+ local legacy_response=$(curl -s --connect-timeout $DEFAULT_TIMEOUT --max-time $DEFAULT_TIMEOUT \
380
+ -X POST \
381
+ -H "Content-Type: application/json" \
382
+ -H "Accept: application/json" \
383
+ -H "User-Agent: VeSync/2.8.6 (Android; SM N9005)" \
384
+ -d "$legacy_payload" \
385
+ "$base_url/cloud/v1/user/login" 2>/dev/null || echo '{"error":"network_error"}')
386
+
387
+ local region_lower=$(echo "$region" | tr '[:upper:]' '[:lower:]')
388
+ save_debug_response "$legacy_response" "legacy_auth_${region_lower}"
389
+
390
+ [[ "$VERBOSE" == "true" ]] && {
391
+ echo -e "\n${CYAN}Raw Response:${NC}"
392
+ if command -v jq >/dev/null 2>&1; then
393
+ echo "$legacy_response" | jq '.'
394
+ else
395
+ echo "$legacy_response"
396
+ fi
397
+ echo ""
398
+ }
399
+
400
+ # Check for network errors
401
+ if echo "$legacy_response" | grep -q '"error":"network_error"' || [[ -z "$legacy_response" ]]; then
402
+ echo -e "${RED}${FAILURE}${NC} Network error - cannot reach legacy authentication endpoint"
403
+ return 1
404
+ fi
405
+
406
+ local response_code=$(extract_json_value "$legacy_response" "code")
407
+ local response_msg=$(extract_json_value "$legacy_response" "msg")
408
+ local token=$(extract_json_value "$legacy_response" "result.token")
409
+ local account_id=$(extract_json_value "$legacy_response" "result.accountID")
410
+ local country_code=$(extract_json_value "$legacy_response" "result.countryCode")
411
+
412
+ log "DEBUG" "Legacy response code: $response_code"
413
+ log "DEBUG" "Legacy response message: $response_msg"
414
+
415
+ if [[ -n "$token" && "$token" != "null" ]]; then
416
+ echo -e "${GREEN}${SUCCESS}${NC} Legacy authentication successful!"
417
+ echo -e " ${ARROW} Token: $(redact_pii "$token" "token")"
418
+ echo -e " ${ARROW} Account ID: $(redact_pii "$account_id" "account_id")"
419
+ echo -e " ${ARROW} Country: ${country_code:-unknown}"
420
+ return 0
421
+ else
422
+ case "$response_code" in
423
+ "-11012022")
424
+ echo -e "${YELLOW}${WARNING}${NC} App version too low error"
425
+ echo -e " ${ARROW} This may indicate API changes requiring newer app version"
426
+ ;;
427
+ "-11260022")
428
+ echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error"
429
+ echo -e " ${ARROW} Account may be registered in different region"
430
+ return 3 # Special return code for cross-region
431
+ ;;
432
+ *)
433
+ explain_error_code "$response_code" "$response_msg"
434
+ ;;
435
+ esac
436
+ return 1
437
+ fi
438
+ }
439
+
440
+ # Function to test authentication for multiple regions
441
+ test_regional_authentication() {
442
+ local auth_type="$1" # "new" or "legacy"
443
+ local regions=("US" "EU")
444
+ local endpoints=("https://smartapi.vesync.com" "https://smartapi.vesync.eu")
445
+ local successful_regions=""
446
+ local failed_regions=""
447
+ local cross_region_errors=""
448
+
449
+ # Initialize global variables to store individual region results
450
+ if [[ "$auth_type" == "new" ]]; then
451
+ NEW_AUTH_US_RESULT=1
452
+ NEW_AUTH_EU_RESULT=1
453
+ else
454
+ LEGACY_AUTH_US_RESULT=1
455
+ LEGACY_AUTH_EU_RESULT=1
456
+ fi
457
+
458
+ for i in "${!regions[@]}"; do
459
+ local region="${regions[i]}"
460
+ local base_url="${endpoints[i]}"
461
+ local result=0
462
+
463
+ echo -e "\n${CYAN}${ARROW} Testing $auth_type authentication for $region region...${NC}"
464
+
465
+ # Test authentication with error handling to prevent script exit
466
+ if [[ "$auth_type" == "new" ]]; then
467
+ test_new_authentication "$base_url" "$region" || result=$?
468
+ if [[ "$region" == "US" ]]; then
469
+ NEW_AUTH_US_RESULT=$result
470
+ else
471
+ NEW_AUTH_EU_RESULT=$result
472
+ fi
473
+ else
474
+ test_legacy_authentication "$base_url" "$region" || result=$?
475
+ if [[ "$region" == "US" ]]; then
476
+ LEGACY_AUTH_US_RESULT=$result
477
+ else
478
+ LEGACY_AUTH_EU_RESULT=$result
479
+ fi
480
+ fi
481
+
482
+ # Display immediate result for this region
483
+ case $result in
484
+ 0)
485
+ echo -e "${GREEN}${SUCCESS}${NC} $auth_type authentication successful for $region region"
486
+ successful_regions="$successful_regions $region"
487
+ ;;
488
+ 2)
489
+ echo -e "${BLUE}${INFO}${NC} $auth_type authentication not supported for $region region (legacy required)"
490
+ failed_regions="$failed_regions $region"
491
+ ;;
492
+ 3)
493
+ echo -e "${YELLOW}${WARNING}${NC} Cross-region authentication error for $region region"
494
+ cross_region_errors="$cross_region_errors $region"
495
+ ;;
496
+ *)
497
+ echo -e "${RED}${FAILURE}${NC} $auth_type authentication failed for $region region"
498
+ failed_regions="$failed_regions $region"
499
+ ;;
500
+ esac
501
+ done
502
+
503
+ # Report overall regional results
504
+ if [[ -n "$successful_regions" ]]; then
505
+ echo -e "\n${GREEN}${SUCCESS}${NC} $auth_type authentication successful in regions:$successful_regions"
506
+ return 0
507
+ elif [[ -n "$cross_region_errors" ]]; then
508
+ echo -e "\n${YELLOW}${WARNING}${NC} Cross-region errors in:$cross_region_errors"
509
+ return 3
510
+ else
511
+ echo -e "\n${RED}${FAILURE}${NC} $auth_type authentication failed in all tested regions"
512
+ return 1
513
+ fi
514
+ }
515
+
516
+ # Function to guess region from email domain
517
+ guess_region_from_email() {
518
+ local email="$1"
519
+ local domain="${email##*@}"
520
+
521
+ # European domains
522
+ local eu_domains=(".de" ".fr" ".it" ".es" ".uk" ".co.uk" ".nl" ".be" ".at" ".ch" ".se" ".no" ".dk" ".fi")
523
+
524
+ for eu_domain in "${eu_domains[@]}"; do
525
+ if [[ "$domain" == *"$eu_domain" ]]; then
526
+ echo "EU"
527
+ return
528
+ fi
529
+ done
530
+
531
+ echo "US"
532
+ }
533
+
534
+ # Function to print section header
535
+ print_section() {
536
+ local title="$1"
537
+ local width=80
538
+ local padding=$((($width - ${#title} - 2) / 2))
539
+
540
+ echo ""
541
+ echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $width))${NC}"
542
+ echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $padding)) ${BOLD}$title${NC}${BLUE} $(printf '═%.0s' $(seq 1 $padding))${NC}"
543
+ echo -e "${BLUE}$(printf '═%.0s' $(seq 1 $width))${NC}"
544
+ }
545
+
546
+ # Function to get result status display
547
+ get_result_status() {
548
+ local result="$1"
549
+ case "$result" in
550
+ 0) echo -e "${GREEN}${SUCCESS} Success${NC}" ;;
551
+ 2) echo -e "${BLUE}${INFO} Legacy Required${NC}" ;;
552
+ 3) echo -e "${YELLOW}${WARNING} Region Mismatch${NC}" ;;
553
+ *) echo -e "${RED}${FAILURE} Failed${NC}" ;;
554
+ esac
555
+ }
556
+
557
+ # Function to print final summary and recommendations
558
+ print_final_summary() {
559
+ local new_auth_result="$1"
560
+ local legacy_auth_result="$2"
561
+ local detected_region="$3"
562
+
563
+ print_section "FINAL SUMMARY & RECOMMENDATIONS"
564
+
565
+ echo -e "${BOLD}Account Information:${NC}"
566
+ echo -e " Email: $(redact_pii "$EMAIL" "email")"
567
+ echo -e " Detected Region: $detected_region"
568
+ echo ""
569
+
570
+ echo -e "${BOLD}Authentication Test Results:${NC}"
571
+ echo -e " NEW Auth US: $(get_result_status "${NEW_AUTH_US_RESULT:-1}")"
572
+ echo -e " NEW Auth EU: $(get_result_status "${NEW_AUTH_EU_RESULT:-1}")"
573
+ echo -e " LEGACY Auth US: $(get_result_status "${LEGACY_AUTH_US_RESULT:-1}")"
574
+ echo -e " LEGACY Auth EU: $(get_result_status "${LEGACY_AUTH_EU_RESULT:-1}")"
575
+ echo ""
576
+
577
+ # Determine which method(s) work
578
+ local working_methods=""
579
+ if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" ]]; then
580
+ working_methods="NEW Authentication"
581
+ fi
582
+ if [[ "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
583
+ if [[ -n "$working_methods" ]]; then
584
+ working_methods="$working_methods, LEGACY Authentication"
585
+ else
586
+ working_methods="LEGACY Authentication"
587
+ fi
588
+ fi
589
+
590
+ if [[ -n "$working_methods" ]]; then
591
+ echo -e "${BOLD}${GREEN}FINAL VERDICT: $working_methods works for this account${NC}"
592
+ else
593
+ echo -e "${BOLD}${RED}FINAL VERDICT: No authentication methods worked${NC}"
594
+ fi
595
+ echo ""
596
+
597
+ # Determine authentication type based on individual region results
598
+ local new_auth_works=false
599
+ local legacy_auth_works=false
600
+
601
+ # Check if NEW auth worked in any region
602
+ if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" ]]; then
603
+ new_auth_works=true
604
+ fi
605
+
606
+ # Check if LEGACY auth worked in any region
607
+ if [[ "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
608
+ legacy_auth_works=true
609
+ fi
610
+
611
+ # Determine authentication type
612
+ if [[ "$new_auth_works" == "true" ]]; then
613
+ echo -e " ${GREEN}${SUCCESS}${NC} ${BOLD}Authentication Type: NEW (PR #340)${NC}"
614
+ echo -e " ${ARROW} Your account supports the new two-step authentication flow"
615
+ echo -e " ${ARROW} Compatible with pyvesync PR #340"
616
+ echo -e " ${ARROW} Uses VeSync app version 5.6.60 or higher"
617
+
618
+ elif [[ "$legacy_auth_works" == "true" ]]; then
619
+ # Check if new auth explicitly required legacy (returned code 2)
620
+ if [[ "${NEW_AUTH_US_RESULT:-1}" == "2" || "${NEW_AUTH_EU_RESULT:-1}" == "2" ]]; then
621
+ echo -e " ${BLUE}${INFO}${NC} ${BOLD}Authentication Type: LEGACY ONLY${NC}"
622
+ echo -e " ${ARROW} Account explicitly requires legacy authentication"
623
+ else
624
+ echo -e " ${YELLOW}${WARNING}${NC} ${BOLD}Authentication Type: LEGACY (Fallback)${NC}"
625
+ echo -e " ${ARROW} New auth failed, but legacy auth works"
626
+ fi
627
+ echo -e " ${ARROW} Uses traditional single-step login flow"
628
+ echo -e " ${ARROW} Compatible with existing pyvesync implementations"
629
+ echo -e " ${ARROW} Uses VeSync app version 2.8.6"
630
+
631
+ elif [[ "${NEW_AUTH_US_RESULT:-1}" == "3" || "${NEW_AUTH_EU_RESULT:-1}" == "3" || "${LEGACY_AUTH_US_RESULT:-1}" == "3" || "${LEGACY_AUTH_EU_RESULT:-1}" == "3" ]]; then
632
+ echo -e " ${YELLOW}${WARNING}${NC} ${BOLD}Authentication Type: REGION MISMATCH${NC}"
633
+ echo -e " ${ARROW} Cross-region authentication errors detected"
634
+ echo -e " ${ARROW} Try different regional endpoints:"
635
+ echo -e " - US: https://smartapi.vesync.com"
636
+ echo -e " - EU: https://smartapi.vesync.eu"
637
+
638
+ else
639
+ echo -e " ${RED}${FAILURE}${NC} ${BOLD}Authentication Type: UNDETERMINED${NC}"
640
+ echo -e " ${ARROW} Both new and legacy authentication failed"
641
+ echo -e " ${ARROW} Verify your email and password are correct"
642
+ echo -e " ${ARROW} Check if your account is locked or suspended"
643
+ fi
644
+
645
+ # Additional troubleshooting info
646
+ echo ""
647
+ echo -e "${BOLD}Troubleshooting:${NC}"
648
+ if [[ "$DEBUG" == "true" ]]; then
649
+ echo -e " • Debug files saved to: ${DEBUG_DIR:-/tmp/vesync-auth-debug-*}"
650
+ fi
651
+ echo -e " • Run with -v flag for verbose API responses"
652
+ echo -e " • Run with -d flag to save debug files"
653
+ echo -e " • Check network connectivity to VeSync endpoints"
654
+
655
+ print_section "SCRIPT COMPLETED"
656
+ }
657
+
658
+ # Main execution function
659
+ main() {
660
+ # Parse command line arguments
661
+ while [[ $# -gt 0 ]]; do
662
+ case $1 in
663
+ -v|--verbose)
664
+ VERBOSE=true
665
+ shift
666
+ ;;
667
+ -d|--debug)
668
+ DEBUG=true
669
+ VERBOSE=true # Debug implies verbose
670
+ shift
671
+ ;;
672
+ -h|--help)
673
+ show_usage
674
+ exit 0
675
+ ;;
676
+ -*)
677
+ echo "Unknown option: $1" >&2
678
+ show_usage
679
+ exit 1
680
+ ;;
681
+ *)
682
+ if [[ -z "$EMAIL" ]]; then
683
+ EMAIL="$1"
684
+ elif [[ -z "$PASSWORD" ]]; then
685
+ PASSWORD="$1"
686
+ else
687
+ echo "Too many arguments" >&2
688
+ show_usage
689
+ exit 1
690
+ fi
691
+ shift
692
+ ;;
693
+ esac
694
+ done
695
+
696
+ # Load environment variables if available
697
+ if [[ -f .env ]]; then
698
+ set -a
699
+ source .env
700
+ set +a
701
+ fi
702
+
703
+ # Use environment variables if arguments not provided
704
+ EMAIL="${EMAIL:-${VESYNC_USERNAME:-}}"
705
+ PASSWORD="${PASSWORD:-${VESYNC_PASSWORD:-}}"
706
+
707
+ # Validate required parameters
708
+ if [[ -z "$EMAIL" || -z "$PASSWORD" ]]; then
709
+ echo -e "${RED}ERROR: Email and password are required${NC}" >&2
710
+ echo "Usage: $0 [email] [password]"
711
+ echo "Or set VESYNC_USERNAME and VESYNC_PASSWORD in .env"
712
+ exit 1
713
+ fi
714
+
715
+ # Setup debug environment
716
+ setup_debug_dir
717
+
718
+ # Generate unique identifiers
719
+ APP_ID=$(generate_app_id)
720
+ TERMINAL_ID=$(generate_terminal_id)
721
+
722
+ # Print header
723
+ print_section "$SCRIPT_NAME v$SCRIPT_VERSION"
724
+
725
+ echo -e "${BOLD}Configuration:${NC}"
726
+ echo -e " Account: $(redact_pii "$EMAIL" "email")"
727
+ echo -e " App ID: $APP_ID"
728
+ echo -e " Terminal ID: $TERMINAL_ID"
729
+ echo -e " Verbose Mode: $VERBOSE"
730
+ echo -e " Debug Mode: $DEBUG"
731
+ [[ "$DEBUG" == "true" ]] && echo -e " Debug Directory: $DEBUG_DIR"
732
+
733
+ # Test connectivity first
734
+ test_connectivity
735
+
736
+ # Guess user's region
737
+ local guessed_region=$(guess_region_from_email "$EMAIL")
738
+ log "INFO" "Detected likely region: $guessed_region"
739
+
740
+ # Test NEW authentication flow first
741
+ print_section "NEW AUTHENTICATION TESTING (PR #340)"
742
+ echo -e "${YELLOW}${INFO}${NC} Testing new authentication flow across all regions..."
743
+ local new_auth_result=1
744
+ test_regional_authentication "new" || new_auth_result=$?
745
+
746
+ echo -e "\n${CYAN}${ARROW} NEW authentication testing completed. Continuing to LEGACY testing...${NC}"
747
+
748
+ # Test LEGACY authentication flow
749
+ print_section "LEGACY AUTHENTICATION TESTING"
750
+ echo -e "${YELLOW}${INFO}${NC} Testing legacy authentication flow across all regions..."
751
+ local legacy_auth_result=1
752
+ test_regional_authentication "legacy" || legacy_auth_result=$?
753
+
754
+ echo -e "\n${CYAN}${ARROW} LEGACY authentication testing completed. Generating final summary...${NC}"
755
+
756
+ # Print final summary and recommendations
757
+ print_final_summary "$new_auth_result" "$legacy_auth_result" "$guessed_region"
758
+
759
+ # Exit with appropriate code based on individual results
760
+ if [[ "${NEW_AUTH_US_RESULT:-1}" == "0" || "${NEW_AUTH_EU_RESULT:-1}" == "0" || "${LEGACY_AUTH_US_RESULT:-1}" == "0" || "${LEGACY_AUTH_EU_RESULT:-1}" == "0" ]]; then
761
+ exit 0 # Success
762
+ elif [[ "${NEW_AUTH_US_RESULT:-1}" == "3" || "${NEW_AUTH_EU_RESULT:-1}" == "3" || "${LEGACY_AUTH_US_RESULT:-1}" == "3" || "${LEGACY_AUTH_EU_RESULT:-1}" == "3" ]]; then
763
+ exit 3 # Region mismatch
764
+ else
765
+ exit 1 # Authentication failed
766
+ fi
767
+ }
768
+
769
+ # Run main function with all arguments
770
+ main "$@"