[api] introduce userauth middleware and helpers

This commit is contained in:
Aleksandr Soloshenko 2025-02-06 06:35:36 +07:00 committed by Aleksandr
parent d9d3657701
commit d19787888c
9 changed files with 122 additions and 42 deletions

View File

@ -5,11 +5,11 @@ import (
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/devices"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/logs"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/webhooks"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/basicauth"
"go.uber.org/fx"
"go.uber.org/zap"
)
@ -46,24 +46,10 @@ func (h *thirdPartyHandler) Register(router fiber.Router) {
h.healthHandler.Register(router)
router.Use(basicauth.New(basicauth.Config{
Authorizer: func(username string, password string) bool {
return len(username) > 0 && len(password) > 0
},
}), func(c *fiber.Ctx) error {
username := c.Locals("username").(string)
password := c.Locals("password").(string)
user, err := h.authSvc.AuthorizeUser(username, password)
if err != nil {
h.Logger.Error("failed to authorize user", zap.Error(err))
return fiber.ErrUnauthorized
}
c.Locals("user", user)
return c.Next()
})
router.Use(
userauth.New(h.authSvc),
userauth.UserRequired(),
)
h.messagesHandler.Register(router.Group("/message")) // TODO: remove after 2025-12-31
h.messagesHandler.Register(router.Group("/messages"))

View File

@ -5,8 +5,8 @@ import (
"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/devices"
"github.com/android-sms-gateway/server/pkg/types"
"github.com/capcom6/go-helpers/slices"
@ -62,7 +62,7 @@ func (h *ThirdPartyController) getDevices(user models.User, c *fiber.Ctx) error
}
func (h *ThirdPartyController) Register(router fiber.Router) {
router.Get("", auth.WithUser(h.getDevices))
router.Get("", userauth.WithUser(h.getDevices))
}
func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {

View File

@ -2,8 +2,8 @@ package logs
import (
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
@ -40,7 +40,7 @@ func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error {
}
func (h *ThirdPartyController) Register(router fiber.Router) {
router.Get("", auth.WithUser(h.get))
router.Get("", userauth.WithUser(h.get))
}
func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {

View File

@ -6,8 +6,8 @@ import (
"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/devices"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
"github.com/go-playground/validator/v10"
@ -163,10 +163,10 @@ func (h *ThirdPartyController) postInboxExport(user models.User, c *fiber.Ctx) e
}
func (h *ThirdPartyController) Register(router fiber.Router) {
router.Post("", auth.WithUser(h.post))
router.Get(":id", auth.WithUser(h.get))
router.Post("", userauth.WithUser(h.post))
router.Get(":id", userauth.WithUser(h.get))
router.Post("inbox/export", auth.WithUser(h.postInboxExport))
router.Post("inbox/export", userauth.WithUser(h.postInboxExport))
}
func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {

View File

@ -0,0 +1,100 @@
package userauth
import (
"encoding/base64"
"strings"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
)
const LocalsUser = "user"
// New returns a middleware that will check if the request contains a valid
// "Authorization" header in the form of "Basic <base64 encoded username:password>".
// If the header is valid, the middleware will authorize the user and store the user
// in the request's Locals under the key LocalsUser. If the header is invalid, the
// middleware will call c.Next() and continue with the request.
func New(authSvc *auth.Service) fiber.Handler {
return func(c *fiber.Ctx) error {
// Get authorization header
auth := c.Get(fiber.HeaderAuthorization)
// Check if the header contains content besides "basic".
if len(auth) <= 6 || !strings.EqualFold(auth[:6], "basic ") {
return c.Next()
}
// Decode the header contents
raw, err := base64.StdEncoding.DecodeString(auth[6:])
if err != nil {
return c.Next()
}
// Get the credentials
creds := utils.UnsafeString(raw)
// Check if the credentials are in the correct form
// which is "username:password".
index := strings.Index(creds, ":")
if index == -1 {
return c.Next()
}
// Get the username and password
username := creds[:index]
password := creds[index+1:]
user, err := authSvc.AuthorizeUser(username, password)
if err != nil {
return c.Next()
}
c.Locals(LocalsUser, user)
return c.Next()
}
}
// HasUser checks if a user is present in the Locals of the given context.
// It returns true if the Locals contain a user under the key LocalsUser,
// otherwise returns false.
func HasUser(c *fiber.Ctx) bool {
return c.Locals(LocalsUser) != nil
}
// GetUser returns the user stored in the Locals under the key LocalsUser.
// It is a convenience function that wraps the call to c.Locals(LocalsUser) and
// casts the result to models.User.
//
// It panics if the value stored in Locals is not a models.User.
func GetUser(c *fiber.Ctx) models.User {
return c.Locals(LocalsUser).(models.User)
}
// UserRequired is a middleware that ensures a user is present in the request's Locals.
// If a user is not found, it returns an unauthorized error, otherwise it passes control
// to the next handler in the stack.
func UserRequired() fiber.Handler {
return func(c *fiber.Ctx) error {
if !HasUser(c) {
return fiber.ErrUnauthorized
}
return c.Next()
}
}
// WithUser is a decorator that provides the current user to the handler.
// It assumes that the user is stored in the Locals under the key LocalsUser.
// If the user is not present, it will panic.
//
// It is a convenience function that wraps the call to GetUser and calls the
// handler with the user as the first argument.
func WithUser(handler func(models.User, *fiber.Ctx) error) fiber.Handler {
return func(c *fiber.Ctx) error {
return handler(GetUser(c), c)
}
}

View File

@ -5,8 +5,8 @@ import (
"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/webhooks"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
@ -104,9 +104,9 @@ func (h *ThirdPartyController) delete(user models.User, c *fiber.Ctx) error {
}
func (h *ThirdPartyController) Register(router fiber.Router) {
router.Get("", auth.WithUser(h.get))
router.Post("", auth.WithUser(h.post))
router.Delete("/:id", auth.WithUser(h.delete))
router.Get("", userauth.WithUser(h.get))
router.Post("", userauth.WithUser(h.post))
router.Delete("/:id", userauth.WithUser(h.delete))
}
func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {

View File

@ -5,12 +5,6 @@ import (
"github.com/gofiber/fiber/v2"
)
func WithUser(handler func(models.User, *fiber.Ctx) error) fiber.Handler {
return func(c *fiber.Ctx) error {
return handler(c.Locals("user").(models.User), c)
}
}
func WithDevice(handler func(models.Device, *fiber.Ctx) error) fiber.Handler {
return func(c *fiber.Ctx) error {
return handler(c.Locals("device").(models.Device), c)

View File

@ -78,5 +78,5 @@ DELETE {{localUrl}}/webhooks/LreFUt-Z3sSq0JufY9uWB HTTP/1.1
Authorization: Basic {{localCredentials}}
###
GET {{localUrl}}/logs?from=2024-08-01T13:19:02.093%2B07:00 HTTP/1.1
GET {{localUrl}}/logs?from=2025-02-05T20:39:46.190%2B07:00 HTTP/1.1
Authorization: Basic {{localCredentials}}

View File

@ -7,10 +7,10 @@
GET {{baseUrl}}/health HTTP/1.1
###
GET {{baseUrl}}/api/3rdparty/v1/health HTTP/1.1
GET {{baseUrl}}/3rdparty/v1/health HTTP/1.1
###
POST {{baseUrl}}/api/3rdparty/v1/messages?skipPhoneValidation=false HTTP/1.1
POST {{baseUrl}}/3rdparty/v1/messages?skipPhoneValidation=false HTTP/1.1
Content-Type: application/json
Authorization: Basic {{credentials}}
@ -25,7 +25,7 @@ Authorization: Basic {{credentials}}
}
###
POST {{baseUrl}}/api/3rdparty/v1/messages HTTP/1.1
POST {{baseUrl}}/3rdparty/v1/messages HTTP/1.1
Content-Type: application/json
Authorization: Basic {{credentials}}
@ -41,7 +41,7 @@ Authorization: Basic {{credentials}}
}
###
GET {{baseUrl}}/api/3rdparty/v1/messages/K56aIsVsQ2rECdv_ajzTd HTTP/1.1
GET {{baseUrl}}/3rdparty/v1/messages/K56aIsVsQ2rECdv_ajzTd HTTP/1.1
Authorization: Basic {{credentials}}
###