package rest import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "quantex.com/qfixpt/src/app" jwttoken "quantex.com/qfixpt/src/common/jwttoken" "quantex.com/qfixpt/src/common/tracerr" ) const ErrorField = "error" func (cont *Controller) PartyAdmin(c *gin.Context) { if cont.GetUser(c).IsPartyAdmin { return } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin"}) } func (cont *Controller) CanSendOrder(c *gin.Context) { if cont.GetUser(c).IsViewer { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Rol Viewer is unauthorized to send orders"}) } } func (cont *Controller) Middleman(c *gin.Context) { if cont.GetUser(c).IsMiddleman { return } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Middleman"}) } func (cont *Controller) BackOfficeUser(c *gin.Context) { if cont.GetUser(c).IsBackOffice { return } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized User"}) } func (cont *Controller) SuperUser(c *gin.Context) { if cont.GetUser(c).IsSuperUser { return } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin User"}) } func (cont *Controller) Options(c *gin.Context) { if c.Request.Method != http.MethodOptions { c.Next() } else { setHeaders(c, cont.config) c.AbortWithStatus(http.StatusOK) } } func (cont *Controller) AuthRequired(ctx *gin.Context) { setHeaders(ctx, cont.config) if c, err := ctx.Cookie("session_token"); c != "" && err == nil { cont.SessionCookieAuth(ctx) return } // check header for Token Auth reqToken := ctx.GetHeader("Authorization") if reqToken != "" { cont.AuthorizationAuth(reqToken, ctx) return } log.Error().Msg("Token Auth Unauthorized: missing session cookie and Authorization header") ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"}) } func (cont *Controller) AuthorizationAuth(reqToken string, ctx *gin.Context) { token := strings.TrimSpace(strings.TrimPrefix(reqToken, "Bearer")) if cont.config.EnableJWTAuth && jwttoken.IsJWT(token) { cont.JWTTokenAuth(token, ctx) return } cont.BearerTokenAuth(token, ctx) } func (cont *Controller) BearerTokenAuth(reqToken string, ctx *gin.Context) { if !strings.HasPrefix(reqToken, "Bearer ") { log.Error().Msg("Token Auth Unauthorized: missing Bearer prefix") ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized"}) return } token := strings.Split(reqToken, "Bearer ") if len(token) != 2 { log.Error().Msg("Token Auth Unauthorized at TokenAuth: invalid token format") ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 1"}) return } user, err := cont.validateUserToken(token[1]) if err != nil { err = errors.New("Token Auth Unauthorized at TokenAuth - %s" + err.Error()) log.Error().Msg(err.Error()) ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 2"}) return } log.Info().Msgf("User %s authenticated successfully at TokenAuth", user.Email) ctx.Set(responseKey, []byte(user.Email)) ctx.Next() } func (cont *Controller) JWTTokenAuth(token string, ctx *gin.Context) { from := ctx.Query("from") serviceAuth, err := jwttoken.Validate(from, token, cont.config.AuthorizedServices) if err != nil || serviceAuth == nil || serviceAuth.Token == nil { err := tracerr.Errorf("invalid token or claims: %w", err) log.Error().Msg(err.Error()) ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: err.Error()}) return } user, err := cont.validateUserToken(*serviceAuth.Token) if err != nil { err = tracerr.Errorf("Token Auth Unauthorized at JWTTokenAuth: %w", err) log.Error().Msg(err.Error()) ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Invalid credentials"}) return } ctx.Set("authorized_service", serviceAuth) ctx.Set(responseKey, []byte(user.Email)) // TODO: will services be treated as users? if not remove this line log.Info().Str("issuer", serviceAuth.Name).Msg("Service authenticated successfully") ctx.Next() } func (cont *Controller) validateUserToken(token string) (user *app.User, err error) { userInfo := strings.Split(token, ":") if len(userInfo) != 2 || userInfo[1] == "" { err = tracerr.Errorf("invalid token format at validateUserToken") log.Error().Msg(err.Error()) return nil, err } email := userInfo[0] user, err = cont.store.UserByEmail(email) if user == nil || err != nil { err = tracerr.Errorf("user not found at validateUserToken: %w", err) log.Error().Msg(err.Error()) return nil, err } tkn := userInfo[1] if user.Token != tkn { err = tracerr.Errorf("invalid token credentials at validateUserToken") log.Error().Msg(err.Error()) return nil, err } log.Info().Str("email", user.Email).Msg("Service user validated successfully") return user, nil } func (cont *Controller) SessionCookieAuth(ctx *gin.Context) { // We can obtain the session token from the requests cookies, which come with every handler sessionToken, err := ctx.Cookie("session_token") if err != nil { if errors.Is(err, http.ErrNoCookie) { // If the cookie is not set, return an unauthorized status ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"}) return } // For any other type of error, return a bad handler status ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ErrorField: "Bad handler - AuthRequired 2"}) return } cont.validateCookieToExternal(sessionToken, ctx) } func (cont *Controller) validateCookieToExternal(sessionToken string, ctx *gin.Context) { ok, err := cont.store.ValidateSession(sessionToken) if err != nil { ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 4"}) return } if !ok { // If the session token is not valid, return an unauthorized error ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 5"}) return } ctx.Next() } func IPWhiteList(whitelist map[string]bool) gin.HandlerFunc { return func(c *gin.Context) { if !whitelist[c.ClientIP()] { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "status": http.StatusForbidden, "message": "Permission denied", }) return } } }