feat: auth handlers
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package dto
|
||||
|
||||
type ChallengeRequest struct {
|
||||
PubKey string `json:"pubKey" validate:"required,eth_pubkey"`
|
||||
}
|
||||
|
||||
type ChallengeResponse struct {
|
||||
Message string `json:"message"`
|
||||
TimeStamp string `json:"timeStamp"`
|
||||
ExpiresAt string `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type AuthenticateRequest struct {
|
||||
PubKey string `json:"pubKey" validate:"required,eth_pubkey"`
|
||||
Signature string `json:"signature" validate:"required,eth_signature"`
|
||||
Message string `json:"message" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
type AuthenticateResponse struct {
|
||||
AuthorizationToken string `json:"authorizationToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refreshToken" validate:"required,jwt"`
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"backend/internal/api/dto"
|
||||
"backend/internal/domain"
|
||||
"backend/internal/usecase"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService usecase.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *usecase.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: *authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GenerateChallenge(c *fiber.Ctx) error {
|
||||
var req dto.ChallengeRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
challenge, err := h.authService.GenerateChallenge(c.Context(), req.PubKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "failed to generate challenge",
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(
|
||||
dto.ChallengeResponse{
|
||||
Message: challenge.Message.String(),
|
||||
TimeStamp: challenge.TimeStamp.String(),
|
||||
ExpiresAt: challenge.ExpiresAt.String(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Authenticate(c *fiber.Ctx) error {
|
||||
var req dto.AuthenticateRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
messageUUID, err := uuid.Parse(req.Message)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid message format",
|
||||
})
|
||||
}
|
||||
|
||||
challenge := &domain.Challenge{
|
||||
Message: messageUUID,
|
||||
TimeStamp: time.Now().UTC(),
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
clientIP := c.IP()
|
||||
userAgent := c.Get("User-Agent")
|
||||
|
||||
userToken, err := h.authService.Authenticate(
|
||||
c.Context(),
|
||||
req.PubKey,
|
||||
req.Signature,
|
||||
challenge,
|
||||
// add chainID to cfg
|
||||
1,
|
||||
clientIP,
|
||||
userAgent,
|
||||
)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(dto.AuthenticateResponse{
|
||||
AuthorizationToken: userToken.AuthorizationToken,
|
||||
RefreshToken: userToken.RefreshToken,
|
||||
ExpiresAt: userToken.ExpiresAt,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"backend/pkg/jwt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const userClaimsKey = "User-Claims"
|
||||
|
||||
func JWTAuthMiddleware(secret []byte) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "missing or invalid Authorization header",
|
||||
})
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
claims, err := jwt.ParseToken(tokenString, secret)
|
||||
if err != nil || claims == nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "invalid or expired token",
|
||||
})
|
||||
}
|
||||
|
||||
c.Locals(userClaimsKey, claims)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserClaims(c *fiber.Ctx) *jwt.UserClaims {
|
||||
claims, _ := c.Locals(userClaimsKey).(*jwt.UserClaims)
|
||||
return claims
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/internal/app"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Run(cfg config.Server, app *app.AppContainer) {
|
||||
fiberApp := fiber.New()
|
||||
api := fiberApp.Group("/api")
|
||||
// register routes here
|
||||
registerPublicRoutes(api, app)
|
||||
|
||||
log.Fatal(fiberApp.Listen(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)))
|
||||
}
|
||||
|
||||
func registerPublicRoutes(router fiber.Router, app *app.AppContainer) {
|
||||
authgroup := router.Group("/auth")
|
||||
|
||||
//TODO: implement handlers
|
||||
authgroup.Post("/challenge")
|
||||
authgroup.Post("/authenticate")
|
||||
}
|
||||
@@ -1 +1,14 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/internal/usecase"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppContainer struct {
|
||||
cfg config.Config
|
||||
dbConn *gorm.DB
|
||||
authService usecase.AuthService
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
type AuthService interface {
|
||||
GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error)
|
||||
Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint) (*UserToken, error)
|
||||
Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error)
|
||||
}
|
||||
|
||||
type authService struct {
|
||||
@@ -61,7 +61,7 @@ func (s *authService) GenerateChallenge(ctx context.Context, pubKey string) (*do
|
||||
return challenge, nil
|
||||
}
|
||||
|
||||
func (s *authService) Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint) (*UserToken, error) {
|
||||
func (s *authService) Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error) {
|
||||
_, err := common.IsValidPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user