This commit is contained in:
“xHuPo” 2025-05-27 17:44:24 +08:00
parent 44500afd3f
commit 5d370e1077
13 changed files with 529 additions and 519 deletions

View file

@ -2,11 +2,9 @@ package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"otpm/api"
"otpm/middleware"
@ -14,7 +12,7 @@ import (
"otpm/services"
)
// OTPHandler handles OTP related requests
// OTPHandler handles OTP-related HTTP requests
type OTPHandler struct {
otpService *services.OTPService
}
@ -26,90 +24,53 @@ func NewOTPHandler(otpService *services.OTPService) *OTPHandler {
}
}
// CreateOTPRequest represents a request to create an OTP
type CreateOTPRequest struct {
Name string `json:"name"`
Issuer string `json:"issuer"`
Secret string `json:"secret"`
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Period int `json:"period"`
// Routes returns the routes for OTP operations
func (h *OTPHandler) Routes() map[string]httprouter.Handle {
return map[string]httprouter.Handle{
"POST /api/otp": h.CreateOTP,
"GET /api/otps": h.ListOTPs,
"GET /api/otp/:id": h.GetOTP,
}
}
// CreateOTP handles OTP creation
func (h *OTPHandler) CreateOTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Limit request body size
r.Body = http.MaxBytesReader(w, r.Body, 10*1024) // 10KB max for OTP creation
// CreateOTP handles the creation of a new OTP
func (h *OTPHandler) CreateOTP(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
userID, ok := r.Context().Value(middleware.UserIDKey).(string)
if !ok {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
log.Printf("CreateOTP unauthorized attempt")
return
}
// Parse request
var req CreateOTPRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
fmt.Sprintf("Invalid request body: %v", err))
log.Printf("CreateOTP request parse error for user %s: %v", userID, err)
// Parse request body
var params models.OTPParams
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
api.NewResponseWriter(w).WriteError(api.ValidationError("Invalid request body"))
return
}
// Validate OTP parameters
if req.Secret == "" {
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
"Secret is required")
log.Printf("CreateOTP validation failed for user %s: empty secret", userID)
return
}
// Validate algorithm
supportedAlgos := map[string]bool{
"SHA1": true,
"SHA256": true,
"SHA512": true,
}
if !supportedAlgos[strings.ToUpper(req.Algorithm)] {
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
"Unsupported algorithm. Supported: SHA1, SHA256, SHA512")
log.Printf("CreateOTP validation failed for user %s: unsupported algorithm %s",
userID, req.Algorithm)
// Validate request
if err := api.Validate.Struct(params); err != nil {
api.NewResponseWriter(w).WriteError(api.ValidationError(err.Error()))
return
}
// Create OTP
otp, err := h.otpService.CreateOTP(r.Context(), userID, models.OTPParams{
Name: req.Name,
Issuer: req.Issuer,
Secret: req.Secret,
Algorithm: req.Algorithm,
Digits: req.Digits,
Period: req.Period,
})
otp, err := h.otpService.CreateOTP(r.Context(), userID, params)
if err != nil {
api.NewResponseWriter(w).WriteError(api.ValidationError(err.Error()))
log.Printf("CreateOTP failed for user %s: %v", userID, err)
api.NewResponseWriter(w).WriteError(api.InternalError(err))
return
}
// Log successful creation (mask secret in logs)
log.Printf("OTP created for user %s (took %v): name=%s issuer=%s algo=%s digits=%d period=%d",
userID, time.Since(start), req.Name, req.Issuer, req.Algorithm, req.Digits, req.Period)
// Return response
api.NewResponseWriter(w).WriteSuccess(otp)
}
// ListOTPs handles listing all OTPs for a user
func (h *OTPHandler) ListOTPs(w http.ResponseWriter, r *http.Request) {
func (h *OTPHandler) ListOTPs(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
userID, ok := r.Context().Value(middleware.UserIDKey).(string)
if !ok {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
return
}
@ -121,166 +82,33 @@ func (h *OTPHandler) ListOTPs(w http.ResponseWriter, r *http.Request) {
return
}
// Return response
api.NewResponseWriter(w).WriteSuccess(otps)
}
// GetOTPCode handles generating OTP code
func (h *OTPHandler) GetOTPCode(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// GetOTP handles getting a specific OTP
func (h *OTPHandler) GetOTP(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
log.Printf("GetOTPCode unauthorized attempt from IP %s", r.RemoteAddr)
return
}
// Get OTP ID from URL
otpID := strings.TrimPrefix(r.URL.Path, "/otp/")
otpID = strings.TrimSuffix(otpID, "/code")
// Validate OTP ID format
if len(otpID) != 36 { // Assuming UUID format
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
"Invalid OTP ID format")
log.Printf("GetOTPCode invalid OTP ID format: %s (user %s)", otpID, userID)
return
}
// Rate limiting check could be added here
// (would require redis or similar rate limiter)
// Generate code
code, expiresIn, err := h.otpService.GenerateCode(r.Context(), otpID, userID)
if err != nil {
api.NewResponseWriter(w).WriteError(api.InternalError(err))
log.Printf("GetOTPCode failed for user %s OTP %s: %v", userID, otpID, err)
return
}
// Log successful generation (without actual code)
log.Printf("OTP code generated for user %s OTP %s (took %v, expires in %ds)",
userID, otpID, time.Since(start), expiresIn)
api.NewResponseWriter(w).WriteSuccess(map[string]interface{}{
"code": code,
"expires_in": expiresIn,
})
}
// VerifyOTPRequest represents a request to verify an OTP code
type VerifyOTPRequest struct {
Code string `json:"code"`
}
// VerifyOTP handles OTP code verification
func (h *OTPHandler) VerifyOTP(w http.ResponseWriter, r *http.Request) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
userID, ok := r.Context().Value(middleware.UserIDKey).(string)
if !ok {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
return
}
// Get OTP ID from URL
otpID := strings.TrimPrefix(r.URL.Path, "/otp/")
otpID = strings.TrimSuffix(otpID, "/verify")
// Parse request
var req VerifyOTPRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams, "Invalid request body")
otpID := ps.ByName("id")
if otpID == "" {
api.NewResponseWriter(w).WriteError(api.ValidationError("Missing OTP ID"))
return
}
// Verify code
valid, err := h.otpService.VerifyCode(r.Context(), otpID, userID, req.Code)
if err != nil {
api.NewResponseWriter(w).WriteError(api.InternalError(err))
return
}
api.NewResponseWriter(w).WriteSuccess(map[string]bool{
"valid": valid,
})
}
// UpdateOTPRequest represents a request to update an OTP
type UpdateOTPRequest struct {
Name string `json:"name"`
Issuer string `json:"issuer"`
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Period int `json:"period"`
}
// UpdateOTP handles OTP update
func (h *OTPHandler) UpdateOTP(w http.ResponseWriter, r *http.Request) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
return
}
// Get OTP ID from URL
otpID := strings.TrimPrefix(r.URL.Path, "/otp/")
// Parse request
var req UpdateOTPRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams, "Invalid request body")
return
}
// Update OTP
otp, err := h.otpService.UpdateOTP(r.Context(), otpID, userID, models.OTPParams{
Name: req.Name,
Issuer: req.Issuer,
Algorithm: req.Algorithm,
Digits: req.Digits,
Period: req.Period,
})
// Get OTP
otp, err := h.otpService.GetOTP(r.Context(), otpID, userID)
if err != nil {
api.NewResponseWriter(w).WriteError(api.InternalError(err))
return
}
// Return response
api.NewResponseWriter(w).WriteSuccess(otp)
}
// DeleteOTP handles OTP deletion
func (h *OTPHandler) DeleteOTP(w http.ResponseWriter, r *http.Request) {
// Get user ID from context
userID, err := middleware.GetUserID(r)
if err != nil {
api.NewResponseWriter(w).WriteError(api.ErrUnauthorized)
return
}
// Get OTP ID from URL
otpID := strings.TrimPrefix(r.URL.Path, "/otp/")
// Delete OTP
if err := h.otpService.DeleteOTP(r.Context(), otpID, userID); err != nil {
api.NewResponseWriter(w).WriteError(api.InternalError(err))
return
}
api.NewResponseWriter(w).WriteSuccess(map[string]string{
"message": "OTP deleted successfully",
})
}
// Routes returns all routes for the OTP handler
func (h *OTPHandler) Routes() map[string]http.HandlerFunc {
return map[string]http.HandlerFunc{
"/otp": h.CreateOTP,
"/otp/": h.ListOTPs,
"/otp/{id}": h.UpdateOTP,
"/otp/{id}/code": h.GetOTPCode,
"/otp/{id}/verify": h.VerifyOTP,
}
}