Files
boc/backend/handlers/auth.go
T

118 lines
2.6 KiB
Go
Raw Normal View History

package handlers
import (
"database/sql"
"encoding/json"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
const tokenExpiry = 24 * time.Hour
type AuthHandler struct {
DB *sql.DB
JWTSecret []byte
}
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
type loginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type userResponse struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Role string `json:"role"`
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var req loginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Email == "" || req.Password == "" {
writeError(w, http.StatusBadRequest, "email and password required")
return
}
var (
id string
name string
role string
passwordHash string
)
err := h.DB.QueryRowContext(r.Context(),
`SELECT id, name, role, password_hash FROM boc_users WHERE email = $1`,
req.Email,
).Scan(&id, &name, &role, &passwordHash)
if err == sql.ErrNoRows {
writeError(w, http.StatusUnauthorized, "invalid credentials")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "internal error")
return
}
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)); err != nil {
writeError(w, http.StatusUnauthorized, "invalid credentials")
return
}
now := time.Now()
claims := Claims{
UserID: id,
Email: req.Email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiry)),
Subject: id,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString(h.JWTSecret)
if err != nil {
writeError(w, http.StatusInternalServerError, "could not sign token")
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"token": signed,
"user": userResponse{
ID: id,
Email: req.Email,
Name: name,
Role: role,
},
})
}
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value("user").(*Claims)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"id": claims.UserID,
"email": claims.Email,
"role": claims.Role,
})
}