diff --git a/server/admin_internal_config.go b/server/admin_internal_config.go index f4e827c..8c97333 100644 --- a/server/admin_internal_config.go +++ b/server/admin_internal_config.go @@ -276,8 +276,9 @@ func (c *adminInternalClient) TenantLoginIPWhitelist(id types.TenantID) (*tenant return &result, nil } -func (c *adminInternalClient) LatestAppRelease(platform, channel string) (*types.AppRelease, error) { +func (c *adminInternalClient) LatestAppRelease(appCode, platform, channel string) (*types.AppRelease, error) { query := url.Values{} + query.Set("app_code", strings.ToLower(strings.TrimSpace(appCode))) query.Set("platform", strings.ToLower(strings.TrimSpace(platform))) query.Set("channel", strings.ToLower(strings.TrimSpace(channel))) var dto struct { diff --git a/server/im_http.go b/server/im_http.go index a0bbeb0..c4eb21e 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -2287,10 +2287,18 @@ func serveIMAppReleaseLatest(wrt http.ResponseWriter, req *http.Request) { } platform := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("platform"))) channel := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("channel"))) + appCode := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("app_code"))) currentVersion := strings.TrimSpace(req.URL.Query().Get("current_version")) if channel == "" { channel = "stable" } + if appCode == "" { + appCode = "im" + } + if !isValidAppCode(appCode) { + writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "app_code"}) + return + } if platform != "ios" && platform != "android" && platform != "web" { writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "platform"}) return @@ -2300,7 +2308,7 @@ func serveIMAppReleaseLatest(wrt http.ResponseWriter, req *http.Request) { writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "current_version"}) return } - release, err := store.AppReleases.Latest(platform, channel) + release, err := store.AppReleases.Latest(appCode, platform, channel) if err != nil { if platform == "web" { logs.Warn.Println("im app release latest: using web fallback", err) @@ -8782,6 +8790,19 @@ func appReleasePayload(release *types.AppRelease, currentVersion string, hasUpda } } +func isValidAppCode(raw string) bool { + if raw == "" || len(raw) > 32 { + return false + } + for _, r := range raw { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' { + continue + } + return false + } + return true +} + func parseNumericAppVersion(raw string) ([]uint64, error) { raw = strings.TrimPrefix(strings.ToLower(strings.TrimSpace(raw)), "v") parts := strings.Split(raw, ".") diff --git a/server/store/store.go b/server/store/store.go index bb5b613..2002c12 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -171,7 +171,7 @@ type AdminInternalConfigProvider interface { ListActiveTenants(limit int) ([]types.Tenant, error) RegistrationSMSEnabled() (bool, error) ThirdPartyConfig(group string, keys ...string) (map[string]string, error) - LatestAppRelease(platform, channel string) (*types.AppRelease, error) + LatestAppRelease(appCode, platform, channel string) (*types.AppRelease, error) ModerationProvider() (*types.ModerationProviderConfig, error) ModerationPolicy(tenantCode string) (*types.ModerationPolicy, error) SensitiveWords(tenantCode string) (*types.SensitiveWordSet, error) @@ -2654,20 +2654,21 @@ type profileMapper struct{} var Profiles ProfilePersistenceInterface type AppReleasePersistenceInterface interface { - Latest(platform, channel string) (*types.AppRelease, error) + Latest(appCode, platform, channel string) (*types.AppRelease, error) } type appReleaseMapper struct{} var AppReleases AppReleasePersistenceInterface -func (appReleaseMapper) Latest(platform, channel string) (*types.AppRelease, error) { +func (appReleaseMapper) Latest(appCode, platform, channel string) (*types.AppRelease, error) { platform = strings.ToLower(strings.TrimSpace(platform)) channel = strings.ToLower(strings.TrimSpace(channel)) - if platform == "" || channel == "" { + appCode = strings.ToLower(strings.TrimSpace(appCode)) + if platform == "" || channel == "" || appCode == "" { return nil, types.ErrMalformed } - cacheKey := platform + ":" + channel + cacheKey := appCode + ":" + platform + ":" + channel if cached, ok := appReleaseConfigCache.get(cacheKey); ok { copy := *cached return ©, nil @@ -2675,7 +2676,7 @@ func (appReleaseMapper) Latest(platform, channel string) (*types.AppRelease, err if adminInternalConfigProvider == nil { return nil, types.ErrUnsupported } - release, err := adminInternalConfigProvider.LatestAppRelease(platform, channel) + release, err := adminInternalConfigProvider.LatestAppRelease(appCode, platform, channel) if err != nil { return nil, err } diff --git a/server/store/types/profile.go b/server/store/types/profile.go index a502af1..ef3e3fe 100644 --- a/server/store/types/profile.go +++ b/server/store/types/profile.go @@ -56,6 +56,7 @@ type UserPrivacyPatch struct { type AppRelease struct { ID int64 + AppCode string Platform string Channel string LatestVersion string