ALL 0.1.0 Code
This commit is contained in:
199
server/internal/handlers/auth.go
Normal file
199
server/internal/handlers/auth.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fpmb/server/internal/database"
|
||||
"github.com/fpmb/server/internal/middleware"
|
||||
"github.com/fpmb/server/internal/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func jwtSecret() []byte {
|
||||
s := os.Getenv("JWT_SECRET")
|
||||
if s == "" {
|
||||
s = "changeme-jwt-secret"
|
||||
}
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func jwtRefreshSecret() []byte {
|
||||
s := os.Getenv("JWT_REFRESH_SECRET")
|
||||
if s == "" {
|
||||
s = "changeme-refresh-secret"
|
||||
}
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func generateTokens(user *models.User) (string, string, error) {
|
||||
accessClaims := &middleware.JWTClaims{
|
||||
UserID: user.ID.Hex(),
|
||||
Email: user.Email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(jwtSecret())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
refreshClaims := &middleware.JWTClaims{
|
||||
UserID: user.ID.Hex(),
|
||||
Email: user.Email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
refreshToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(jwtRefreshSecret())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return accessToken, refreshToken, nil
|
||||
}
|
||||
|
||||
func Register(c *fiber.Ctx) error {
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
|
||||
}
|
||||
if body.Name == "" || body.Email == "" || body.Password == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name, email and password are required"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
col := database.GetCollection("users")
|
||||
existing := col.FindOne(ctx, bson.M{"email": body.Email})
|
||||
if existing.Err() == nil {
|
||||
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Email already in use"})
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to hash password"})
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
user := &models.User{
|
||||
ID: primitive.NewObjectID(),
|
||||
Name: body.Name,
|
||||
Email: body.Email,
|
||||
PasswordHash: string(hash),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if _, err := col.InsertOne(ctx, user); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create user"})
|
||||
}
|
||||
|
||||
access, refresh, err := generateTokens(user)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
"user": fiber.Map{"id": user.ID, "name": user.Name, "email": user.Email},
|
||||
})
|
||||
}
|
||||
|
||||
func Login(c *fiber.Ctx) error {
|
||||
var body struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
|
||||
}
|
||||
if body.Email == "" || body.Password == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Email and password are required"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var user models.User
|
||||
err := database.GetCollection("users").FindOne(ctx, bson.M{"email": body.Email}).Decode(&user)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(body.Password)); err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
|
||||
}
|
||||
|
||||
access, refresh, err := generateTokens(&user)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": refresh,
|
||||
"user": fiber.Map{"id": user.ID, "name": user.Name, "email": user.Email},
|
||||
})
|
||||
}
|
||||
|
||||
func RefreshToken(c *fiber.Ctx) error {
|
||||
var body struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil || body.RefreshToken == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "refresh_token is required"})
|
||||
}
|
||||
|
||||
claims := &middleware.JWTClaims{}
|
||||
token, err := jwt.ParseWithClaims(body.RefreshToken, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fiber.ErrUnauthorized
|
||||
}
|
||||
return jwtRefreshSecret(), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired refresh token"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
userID, err := primitive.ObjectIDFromHex(claims.UserID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid token claims"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.GetCollection("users").FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User not found"})
|
||||
}
|
||||
|
||||
access, newRefresh, err := generateTokens(&user)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"access_token": access,
|
||||
"refresh_token": newRefresh,
|
||||
})
|
||||
}
|
||||
|
||||
func Logout(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"message": "Logged out successfully"})
|
||||
}
|
||||
Reference in New Issue
Block a user