feat: replace placeholder auth with db hash
This commit is contained in:
@@ -126,6 +126,7 @@ func main() {
|
|||||||
r.GET("/health", h.Health)
|
r.GET("/health", h.Health)
|
||||||
r.GET("/hello", h.Hello)
|
r.GET("/hello", h.Hello)
|
||||||
r.POST("/login", h.Login)
|
r.POST("/login", h.Login)
|
||||||
|
r.POST("/register", h.Register)
|
||||||
r.GET("/devices", h.GetDevices)
|
r.GET("/devices", h.GetDevices)
|
||||||
|
|
||||||
// mqttping endpoint handled by internal/handler (protected)
|
// mqttping endpoint handled by internal/handler (protected)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@@ -93,7 +94,7 @@ func (h *Handler) Hello(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"message": "hello from lambdaiot"})
|
c.JSON(http.StatusOK, gin.H{"message": "hello from lambdaiot"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login issues a JWT token for demo purposes
|
// Login issues a JWT token after validating credentials against the database
|
||||||
func (h *Handler) Login(c *gin.Context) {
|
func (h *Handler) Login(c *gin.Context) {
|
||||||
var req struct {
|
var req struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
@@ -103,15 +104,31 @@ func (h *Handler) Login(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid json"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid json"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// demo credentials
|
|
||||||
if req.Username != "admin" || req.Password != "password" {
|
// Query user from database
|
||||||
|
var userID string
|
||||||
|
var passwordHash string
|
||||||
|
err := h.DB.QueryRow("SELECT BIN_TO_UUID(id), password_hash FROM users WHERE username = ?", req.Username).Scan(&userID, &passwordHash)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// create token
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify password
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)); err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create token
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"sub": req.Username,
|
"sub": req.Username,
|
||||||
"exp": time.Now().Add(time.Hour).Unix(),
|
"user_id": userID,
|
||||||
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
})
|
})
|
||||||
signed, err := token.SignedString([]byte(h.JWTSecret))
|
signed, err := token.SignedString([]byte(h.JWTSecret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -841,3 +858,46 @@ func (h *Handler) DeleteSensorReading(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "sensor reading deleted"})
|
c.JSON(http.StatusOK, gin.H{"message": "sensor reading deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register creates a new user account
|
||||||
|
func (h *Handler) Register(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||||
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username already exists
|
||||||
|
var exists int
|
||||||
|
err := h.DB.QueryRow("SELECT COUNT(*) FROM users WHERE username = ?", req.Username).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exists > 0 {
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": "username already exists"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert user
|
||||||
|
userID := uuid.New()
|
||||||
|
_, err = h.DB.Exec("INSERT INTO users (id, username, password_hash, email) VALUES (UUID_TO_BIN(?), ?, ?, ?)",
|
||||||
|
userID.String(), req.Username, string(hashedPassword), req.Email)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{"id": userID.String(), "username": req.Username})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
-- Users table for authentication
|
||||||
|
CREATE TABLE users (
|
||||||
|
id BINARY(16) NOT NULL PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255),
|
||||||
|
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- Create index on username for faster lookups
|
||||||
|
CREATE INDEX idx_username ON users(username);
|
||||||
|
|
||||||
|
-- Insert default admin user (password: password)
|
||||||
|
-- bcrypt hash of "password"
|
||||||
|
INSERT INTO users (id, username, password_hash, email)
|
||||||
|
VALUES (
|
||||||
|
UUID_TO_BIN(UUID()),
|
||||||
|
'admin',
|
||||||
|
'$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
|
||||||
|
'admin@example.com'
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user