From a21b554fc7b4de8b55caac5982d3750ae7d12e69 Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 29 Jan 2026 14:33:26 +0700 Subject: [PATCH] [FIX/BE-US] changes permission to redis and scope --- internal/config/config.go | 5 +++ .../modules/sso/controllers/sso.controller.go | 21 +++++++-- .../sso/controllers/user_sync.controller.go | 6 +++ internal/modules/sso/verifier/profile.go | 44 ++++++++++++++----- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 71fb430c..af723b3b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -61,6 +61,7 @@ var ( SSOCookieDomain string SSOCookieSecure bool SSOCookieSameSite string + SSOAccessTokenMaxBytes int SSOTokenBlacklistPrefix string SSOPKCETTL time.Duration SSOUserSyncDrift time.Duration @@ -144,6 +145,10 @@ func init() { SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN") SSOCookieSecure = viper.GetBool("SSO_COOKIE_SECURE") SSOCookieSameSite = defaultString(viper.GetString("SSO_COOKIE_SAMESITE"), "Lax") + SSOAccessTokenMaxBytes = viper.GetInt("SSO_ACCESS_TOKEN_MAX_BYTES") + if SSOAccessTokenMaxBytes <= 0 { + SSOAccessTokenMaxBytes = 4096 + } SSOTokenBlacklistPrefix = defaultString(viper.GetString("SSO_TOKEN_BLACKLIST_PREFIX"), "sso:blacklist") if ttl := viper.GetInt("SSO_PKCE_TTL_SECONDS"); ttl > 0 { SSOPKCETTL = time.Duration(ttl) * time.Second diff --git a/internal/modules/sso/controllers/sso.controller.go b/internal/modules/sso/controllers/sso.controller.go index b49d73e5..5e75d4a9 100644 --- a/internal/modules/sso/controllers/sso.controller.go +++ b/internal/modules/sso/controllers/sso.controller.go @@ -200,7 +200,7 @@ func (h *Controller) Refresh(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusUnauthorized, "invalid access token") } - issueCookies(c, struct { + if err := issueCookies(c, struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` TokenType string `json:"token_type"` @@ -218,7 +218,9 @@ func (h *Controller) Refresh(c *fiber.Ctx) error { IDToken: tokenResp.IDToken, Error: tokenResp.Error, Description: tokenResp.Description, - }, verification) + }, verification); err != nil { + return err + } utils.Log.WithFields(logrus.Fields{ "user_id": verification.UserID, @@ -307,7 +309,9 @@ func (h *Controller) Callback(c *fiber.Ctx) error { } // prepare cookies - issueCookies(c, tokenResp, verification) + if err := issueCookies(c, tokenResp, verification); err != nil { + return err + } redirectTarget := sessionData.ReturnTo if redirectTarget == "" { @@ -742,13 +746,21 @@ func issueCookies(c *fiber.Ctx, tokenResp struct { IDToken string `json:"id_token"` Error string `json:"error"` Description string `json:"error_description"` -}, verification *sso.VerificationResult) { +}, verification *sso.VerificationResult) error { if revoker := session.GetRevocationStore(); revoker != nil && verification != nil { if err := revoker.ClearUserLogout(c.Context(), verification.UserID); err != nil { utils.Log.WithError(err).Warn("failed to clear logout marker") } } + if max := config.SSOAccessTokenMaxBytes; max > 0 && len(tokenResp.AccessToken) > max { + utils.Log.WithFields(logrus.Fields{ + "token_len": len(tokenResp.AccessToken), + "max_len": max, + }).Warn("sso access token exceeds cookie size limit") + return fiber.NewError(fiber.StatusRequestEntityTooLarge, "access token too large") + } + accessName := resolveSSOCookieName(config.SSOAccessCookieName, "access") refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh") maxAge := tokenResp.ExpiresIn @@ -790,6 +802,7 @@ func issueCookies(c *fiber.Ctx, tokenResp struct { // Optional: expose limited info via headers for FE debugging (avoid tokens) c.Set("X-Auth-User", fmt.Sprintf("%d", verification.UserID)) + return nil } func clearSSOCookie(c *fiber.Ctx, name string) { diff --git a/internal/modules/sso/controllers/user_sync.controller.go b/internal/modules/sso/controllers/user_sync.controller.go index 72c7768a..bdc7900e 100644 --- a/internal/modules/sso/controllers/user_sync.controller.go +++ b/internal/modules/sso/controllers/user_sync.controller.go @@ -291,6 +291,8 @@ func (h *UserSyncController) upsertUser(c *fiber.Ctx, alias string, req *userSyn "user_id": req.User.ID, }).Info("sso user synced") + sso.InvalidateProfileCache(c.Context(), uint(req.User.ID)) + msg := fmt.Sprintf("User %s successfully", req.Action) return c.Status(fiber.StatusOK).JSON(response.Success{ Code: fiber.StatusOK, @@ -318,6 +320,8 @@ func (h *UserSyncController) logoutUser(c *fiber.Ctx, alias string, req *userSyn "user_id": req.User.ID, }).Info("sso user logout enforced") + sso.InvalidateProfileCache(c.Context(), uint(req.User.ID)) + return c.Status(fiber.StatusOK).JSON(response.Common{ Code: fiber.StatusOK, Status: "success", @@ -341,6 +345,8 @@ func (h *UserSyncController) removeUser(c *fiber.Ctx, alias string, req *userSyn "user_id": req.User.ID, }).Info("sso user deleted") + sso.InvalidateProfileCache(c.Context(), uint(req.User.ID)) + return c.Status(fiber.StatusOK).JSON(response.Common{ Code: fiber.StatusOK, Status: "success", diff --git a/internal/modules/sso/verifier/profile.go b/internal/modules/sso/verifier/profile.go index e3cd40ca..4876db1e 100644 --- a/internal/modules/sso/verifier/profile.go +++ b/internal/modules/sso/verifier/profile.go @@ -265,24 +265,44 @@ func profileCacheKey(userID uint) string { return profileCachePrefix + strconv.FormatUint(uint64(userID), 10) } +// InvalidateProfileCache clears cached profile data for the given user in both local and Redis caches. +func InvalidateProfileCache(ctx context.Context, userID uint) { + if userID == 0 { + return + } + key := profileCacheKey(userID) + profileLocalCache.Delete(key) + + client := cache.Redis() + if client == nil { + return + } + if ctx == nil { + ctx = context.Background() + } + if err := client.Del(ctx, key).Err(); err != nil && !errors.Is(err, redis.Nil) { + utils.Log.WithError(err).Warn("sso profile redis delete failed") + } +} + func canonicalPermissionName(name string) string { return strings.ToLower(strings.TrimSpace(name)) } // userInfoEnvelope handles the varying shapes returned by the SSO userinfo endpoint. type userInfoEnvelope struct { - Roles []userInfoRole `json:"roles"` - AreaIDs []uint `json:"area_ids"` - LocationIDs []uint `json:"location_ids"` - AllArea bool `json:"all_area"` - AllLocation bool `json:"all_location"` - Data *struct { - ID int64 `json:"id"` - Roles []userInfoRole `json:"roles"` - AreaIDs []uint `json:"area_ids"` - LocationIDs []uint `json:"location_ids"` - AllArea bool `json:"all_area"` - AllLocation bool `json:"all_location"` + Roles []userInfoRole `json:"roles"` + AreaIDs []uint `json:"area_ids"` + LocationIDs []uint `json:"location_ids"` + AllArea bool `json:"all_area"` + AllLocation bool `json:"all_location"` + Data *struct { + ID int64 `json:"id"` + Roles []userInfoRole `json:"roles"` + AreaIDs []uint `json:"area_ids"` + LocationIDs []uint `json:"location_ids"` + AllArea bool `json:"all_area"` + AllLocation bool `json:"all_location"` } `json:"data"` User *struct { ID int64 `json:"id"`