Spring Security 6 MvcRequestMatcher (used when no HttpMethod is specified
and Spring MVC is on the classpath) fails to match the upload endpoint,
falling through to anyRequest().authenticated() and returning 401.
Specifying HttpMethod forces AntRequestMatcher which matches reliably.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit (GET-only permitAll) inadvertently broke upload by
requiring auth. The original design intentionally allows unauthenticated
upload — explicitly permit POST /api/file/upload to make this clear.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upload endpoint (POST) was inadvertently matched by the method-less
requestMatchers("/api/file/*") rule. Making it GET-only makes the intent
explicit and ensures upload correctly requires a valid JWT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Webhook notification body shows app display name (resolved from
tenant-service via internal API with in-memory cache) instead of appKey
- When re-uploading a package with the same versionCode, automatically
withdraw APPROVED store entries from the older entity before submitting
the new entity, preventing duplicate active submissions
- tenant-service /internal/sdk/apps/{appKey}/platform-info now includes
the app 'name' field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spring Security's default Http403ForbiddenEntryPoint was returning 403
for all auth failures. Frontend clients treat 403 as a permission error
(not an auth error), so silent loops occurred instead of proper re-login.
Adding a custom AuthenticationEntryPoint that returns 401 makes clients
handle auth failures correctly (show login page on 401).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The MI /devupload/dev/query API returns the currently-published app's status,
not the pending submission. appStatus=3 means the previous version is still
online, not that the new submission was accepted. Use updateVersion=true as
the approval signal; default updateVersion to false to avoid false positives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HONOR: use get-app-current-release endpoint (correct), auditResult field (0=review,1=approved,2=rejected)
- HONOR: assertHonorSuccess now accepts both "0" and "0000" success codes
- OPPO: add integer status mapping (111=approved, 444=rejected) from reference impl
- All stores: add full response body logging for diagnosing poll issues
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StoreSubmissionService: skip UNDER_REVIEW/APPROVED stores in preflight loop to prevent duplicate submissions
- StoreSubmissionService: post-batch sweep marks any still-SUBMITTING stores in same batch as REJECTED
- StoreSubmissionService: @EventListener(ApplicationReadyEvent) clears orphaned SUBMITTING states on startup
- AppVersionRepository: add findAllWithSubmittingStores() native query for startup sweep
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OpsController/OpsService: POST /api/ops/apps/{appKey}/transfer to move app between tenants
- StoreSubmissionService: read parallelStoreUpload from publish config; conditional parallel vs sequential submission
- AppStoreService: support DINGTALK/WECOM/FEISHU/CUSTOM notify formats in sendWebhook()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LicenseDeviceService: update app_key on re-registration if blank,
fixing devices that registered before the app_key column was added
- FeatureServiceManager: send activation IM notification in afterCommit()
hook so the frontend refresh sees the committed DB state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RestTemplate drops large multipart payloads on the MI API server; switching to
ProcessBuilder curl with Expect:100-continue headers and a 130-minute timeout resolves
upload failures for large APKs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>