Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/sms-gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import (
)

// @securitydefinitions.basic ApiAuth
// @description User authentication

// @securitydefinitions.apikey MobileToken
// @in header
// @name Authorization
// @description Mobile device token

// @securitydefinitions.apikey ServerKey
// @in header
// @name Authorization
// @description Private server authentication

// @title SMS Gateway for Android™ API
// @version {APP_VERSION}
// @description This API provides programmatic access to sending SMS messages on Android devices. Features include sending SMS, checking message status, device management, webhook configuration, and system health checks.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.22.0

require (
firebase.google.com/go/v4 v4.12.1
github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb
github.com/android-sms-gateway/client-go v1.5.2
github.com/ansrivas/fiberprometheus/v2 v2.6.1
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3
github.com/capcom6/go-helpers v0.1.1
github.com/capcom6/go-infra-fx v0.2.0
github.com/go-playground/assert/v2 v2.2.0
github.com/go-playground/validator/v10 v10.16.0
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb h1:c3ll8h375G/oL4Qzexo35XBxHrw9HgGOqmxK6CPX5Bg=
github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/android-sms-gateway/client-go v1.5.0 h1:CDREtWU2Z85dW7JcsW3a+vKZkj9g2Buq8vlrnEdGaoE=
github.com/android-sms-gateway/client-go v1.5.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/android-sms-gateway/client-go v1.5.1 h1:+bgRoDHcoUe+VvnA7roW+BjNQQC/ZXKd5Rds5rXG4wM=
github.com/android-sms-gateway/client-go v1.5.1/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/android-sms-gateway/client-go v1.5.2 h1:Vk9KZqFiecOjLTac8NWEViK0/Q1FNlfI28DqFlGD6Bk=
github.com/android-sms-gateway/client-go v1.5.2/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/ansrivas/fiberprometheus/v2 v2.6.1 h1:wac3pXaE6BYYTF04AC6K0ktk6vCD+MnDOJZ3SK66kXM=
Expand All @@ -39,6 +45,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3 h1:mq9rmBMCCzqGnZtbQqFSd+Ua3fahqUOYaTf26YFhWJc=
github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw=
github.com/capcom6/go-helpers v0.1.0 h1:eootTmUSCzlu8xOHJfDlT1AasVWsgzZ9grhDxovMtwY=
github.com/capcom6/go-helpers v0.1.0/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw=
github.com/capcom6/go-helpers v0.1.1 h1:kcpK1+VUwo94MZlZX+0Gab4gf78egHTPzW9sOQXLfFE=
github.com/capcom6/go-helpers v0.1.1/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw=
github.com/capcom6/go-infra-fx v0.2.0 h1:FrWtdFiG58unIK7xN7kMJn3LfOFecp20W/ZVgvN3bsM=
github.com/capcom6/go-infra-fx v0.2.0/go.mod h1:T/DnT1EDrF9F+44eZw/lZnmsz5Dry0w/CTk0FB1Nct0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
Expand Down
24 changes: 5 additions & 19 deletions internal/sms-gateway/handlers/3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"))
Expand Down
11 changes: 4 additions & 7 deletions internal/sms-gateway/handlers/converters/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package converters
import (
"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/capcom6/go-helpers/anys"
)

func DeviceToDTO(device *models.Device) *smsgateway.Device {
if device.IsEmpty() {
return nil
}

return &smsgateway.Device{
func DeviceToDTO(device models.Device) smsgateway.Device {
return smsgateway.Device{
ID: device.ID,
Name: *device.Name,
Name: anys.OrDefault(device.Name, ""),
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
DeletedAt: device.DeletedAt,
Expand Down
28 changes: 17 additions & 11 deletions internal/sms-gateway/handlers/converters/devices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/converters"
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"github.com/android-sms-gateway/server/pkg/types"
"github.com/capcom6/go-helpers/anys"
"github.com/go-playground/assert/v2"
)

Expand All @@ -18,26 +18,26 @@ func TestDeviceToDTO(t *testing.T) {

tests := []struct {
name string
device *models.Device
expected *smsgateway.Device
device models.Device
expected smsgateway.Device
}{
{
name: "empty device",
device: &models.Device{},
expected: nil,
device: models.Device{},
expected: smsgateway.Device{},
},
{
name: "non-empty device",
device: &models.Device{
device: models.Device{
ID: "test-id",
Name: types.AsPointer("test-name"),
Name: anys.AsPointer("test-name"),
LastSeen: lastSeenAt,
TimedModel: models.TimedModel{
CreatedAt: createdAt,
UpdatedAt: updatedAt,
},
},
expected: &smsgateway.Device{
expected: smsgateway.Device{
ID: "test-id",
Name: "test-name",
CreatedAt: createdAt,
Expand All @@ -46,9 +46,15 @@ func TestDeviceToDTO(t *testing.T) {
},
},
{
name: "nil device",
device: nil,
expected: nil,
name: "device with nil name",
device: models.Device{
ID: "test-id",
Name: nil,
},
expected: smsgateway.Device{
ID: "test-id",
Name: "",
},
},
}

Expand Down
54 changes: 37 additions & 17 deletions internal/sms-gateway/handlers/devices/3rdparty.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package devices

import (
"errors"
"fmt"

"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/converters"
"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"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
Expand All @@ -32,7 +32,7 @@ type ThirdPartyController struct {
// @Summary List devices
// @Description Returns list of registered devices
// @Security ApiAuth
// @Tags User
// @Tags User, Devices
// @Produce json
// @Success 200 {object} []smsgateway.Device "Device list"
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
Expand All @@ -41,28 +41,48 @@ type ThirdPartyController struct {
// @Router /3rdparty/v1/devices [get]
//
// List devices
func (h *ThirdPartyController) getDevices(user models.User, c *fiber.Ctx) error {
devices, err := h.devicesSvc.Select(devices.WithUserID(user.ID))
func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error {
devices, err := h.devicesSvc.Select(user.ID)
if err != nil {
return fmt.Errorf("can't select devices: %w", err)
}

response := slices.Map(devices, func(device models.Device) smsgateway.Device {
return smsgateway.Device{
ID: device.ID,
Name: types.OrDefault(device.Name, ""),
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
DeletedAt: device.DeletedAt,
LastSeen: device.LastSeen,
}
})
response := slices.Map(devices, converters.DeviceToDTO)

return c.JSON(response)
}

// @Summary Remove device
// @Description Removes device
// @Security ApiAuth
// @Tags User, Devices
// @Produce json
// @Param id path string true "Device ID"
// @Success 204 "Successfully removed"
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
// @Failure 404 {object} smsgateway.ErrorResponse "Device not found"
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
// @Router /3rdparty/v1/devices/{id} [delete]
//
// Remove device
func (h *ThirdPartyController) remove(user models.User, c *fiber.Ctx) error {
id := c.Params("id")

err := h.devicesSvc.Remove(user.ID, devices.WithID(id))
if errors.Is(err, devices.ErrNotFound) {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
if err != nil {
return fmt.Errorf("can't remove device: %w", err)
}

return c.SendStatus(fiber.StatusNoContent)
}

func (h *ThirdPartyController) Register(router fiber.Router) {
router.Get("", auth.WithUser(h.getDevices))
router.Get("", userauth.WithUser(h.get))
router.Delete(":id", userauth.WithUser(h.remove))
}

func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {
Expand Down
2 changes: 1 addition & 1 deletion internal/sms-gateway/handlers/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/health"
"github.com/android-sms-gateway/server/internal/version"
"github.com/android-sms-gateway/server/pkg/maps"
"github.com/capcom6/go-helpers/maps"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
"go.uber.org/zap"
Expand Down
4 changes: 2 additions & 2 deletions internal/sms-gateway/handlers/logs/3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 13 additions & 8 deletions internal/sms-gateway/handlers/messages/3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ 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/capcom6/go-helpers/slices"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
Expand Down Expand Up @@ -38,7 +39,7 @@ type ThirdPartyController struct {
}

// @Summary Enqueue message
// @Description Enqueues message for sending. If ID is not specified, it will be generated
// @Description Enqueues message for sending. If multiple devices are registered, it will be sent via a random one
// @Security ApiAuth
// @Tags User, Messages
// @Accept json
Expand All @@ -62,7 +63,7 @@ func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error {

skipPhoneValidation := c.QueryBool("skipPhoneValidation", false)

devices, err := h.devicesSvc.Select(devices.WithUserID(user.ID))
devices, err := h.devicesSvc.Select(user.ID)
if err != nil {
h.Logger.Error("Failed to select devices", zap.Error(err), zap.String("user_id", user.ID))
return fiber.NewError(fiber.StatusInternalServerError, "Can't select devices. Please contact support")
Expand All @@ -72,7 +73,11 @@ func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "No devices registered")
}

device := devices[0]
device, err := slices.Random(devices)
if err != nil {
return fmt.Errorf("can't get random device: %w", err)
}

state, err := h.messagesSvc.Enqeue(device, req, messages.EnqueueOptions{SkipPhoneValidation: skipPhoneValidation})
if err != nil {
var errValidation messages.ErrValidation
Expand Down Expand Up @@ -146,7 +151,7 @@ func (h *ThirdPartyController) postInboxExport(user models.User, c *fiber.Ctx) e
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}

device, err := h.devicesSvc.Get(devices.WithUserID(user.ID), devices.WithID(req.DeviceID))
device, err := h.devicesSvc.Get(user.ID, devices.WithID(req.DeviceID))
if err != nil {
if errors.Is(err, devices.ErrNotFound) {
return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")
Expand All @@ -163,10 +168,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 {
Expand Down
Loading
Loading