mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 05:21:57 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into dev/teguh
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user