package handlers import ( "encoding/json" "fmt" "log" "net/http" "strings" "time" "otpm/api" "otpm/middleware" "otpm/models" "otpm/services" ) // OTPHandler handles OTP related requests type OTPHandler struct { otpService *services.OTPService } // NewOTPHandler creates a new OTPHandler func NewOTPHandler(otpService *services.OTPService) *OTPHandler { return &OTPHandler{ otpService: otpService, } } // 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"` } // 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 // Get user ID from context userID, err := middleware.GetUserID(r) if err != nil { 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) 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) 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, }) if err != nil { api.NewResponseWriter(w).WriteError(api.ValidationError(err.Error())) log.Printf("CreateOTP failed for user %s: %v", userID, 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) api.NewResponseWriter(w).WriteSuccess(otp) } // ListOTPs handles listing all OTPs for a user func (h *OTPHandler) ListOTPs(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 OTPs otps, err := h.otpService.ListOTPs(r.Context(), userID) if err != nil { api.NewResponseWriter(w).WriteError(api.InternalError(err)) return } api.NewResponseWriter(w).WriteSuccess(otps) } // GetOTPCode handles generating OTP code func (h *OTPHandler) GetOTPCode(w http.ResponseWriter, r *http.Request) { start := time.Now() // 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 { 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") 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, }) if err != nil { api.NewResponseWriter(w).WriteError(api.InternalError(err)) return } 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, } }