mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/sso-adjustment' into 'development'
Feat/be/sso adjustment See merge request mbugroup/lti-api!273
This commit is contained in:
@@ -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