Feat(BE-69,70,71,72,73): crud and integration sso with lti, revoke_token

This commit is contained in:
ragilap
2025-10-21 20:31:10 +07:00
parent e239246d02
commit ab8c5d2ec4
6 changed files with 119 additions and 16 deletions
@@ -9,18 +9,19 @@ import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"strconv"
"strings"
"sync"
"time"
"gitlab.com/mbugroup/lti-api.git/internal/config"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
"gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
userRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/response"
@@ -51,7 +52,7 @@ type UserSyncController struct {
}
type userSyncRequest struct {
Action string `json:"action" validate:"required,oneof=create update delete"`
Action string `json:"action" validate:"required,oneof=create update delete logout"`
PublicID string `json:"public_id" validate:"required"`
User userSyncUser `json:"user" validate:"required"`
}
@@ -134,7 +135,7 @@ func (h *UserSyncController) Sync(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "public_id mismatch with configured client")
}
if req.Action != "delete" {
if req.Action == "create" || req.Action == "update" {
if req.User.Email == "" || req.User.Name == "" {
return fiber.NewError(fiber.StatusBadRequest, "email and name are required for create/update actions")
}
@@ -152,6 +153,8 @@ func (h *UserSyncController) Sync(c *fiber.Ctx) error {
return h.upsertUser(c, alias, req)
case "delete":
return h.removeUser(c, alias, req)
case "logout":
return h.logoutUser(c, alias, req)
default:
return fiber.NewError(fiber.StatusBadRequest, "unsupported action")
}
@@ -231,11 +234,11 @@ func (h *UserSyncController) authenticate(c *fiber.Ctx, body []byte) (string, co
}
func (h *UserSyncController) verifyAuthorization(c *fiber.Ctx, alias string) error {
authHeader := strings.TrimSpace(c.Get(fiber.HeaderAuthorization))
if authHeader == "" {
return fiber.NewError(fiber.StatusUnauthorized, "missing authorization header")
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return fiber.NewError(fiber.StatusUnauthorized, "invalid authorization header")
@@ -254,7 +257,6 @@ func (h *UserSyncController) verifyAuthorization(c *fiber.Ctx, alias string) err
if verification.ServiceAlias == "" || verification.ServiceAlias != alias {
return fiber.NewError(fiber.StatusUnauthorized, "service subject mismatch")
}
if !containsScope(verification.Claims.Scopes(), "sync.users") {
return fiber.NewError(fiber.StatusForbidden, "missing sync scope")
}
@@ -297,6 +299,31 @@ func (h *UserSyncController) upsertUser(c *fiber.Ctx, alias string, req *userSyn
})
}
func (h *UserSyncController) logoutUser(c *fiber.Ctx, alias string, req *userSyncRequest) error {
revoker := session.GetRevocationStore()
if revoker != nil {
if err := revoker.MarkUserLogout(c.Context(), uint(req.User.ID), time.Now().UTC()); err != nil {
h.log.WithError(err).Error("sso user logout revoke failed")
return fiber.NewError(fiber.StatusInternalServerError, "failed to revoke user session")
}
} else {
h.log.Warn("sso user logout received but revocation store not configured")
}
h.log.WithFields(logrus.Fields{
"action": req.Action,
"public_id": req.PublicID,
"alias": alias,
"user_id": req.User.ID,
}).Info("sso user logout enforced")
return c.Status(fiber.StatusOK).JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "User sessions revoked successfully",
})
}
func (h *UserSyncController) removeUser(c *fiber.Ctx, alias string, req *userSyncRequest) error {
if err := h.repo.SoftDeleteByIdUser(c.Context(), req.User.ID); err != nil {
if err == gorm.ErrRecordNotFound {