error
This commit is contained in:
parent
44500afd3f
commit
5d370e1077
13 changed files with 529 additions and 519 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"otpm/services"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication related requests
|
||||
|
@ -27,7 +28,7 @@ func NewAuthHandler(authService *services.AuthService) *AuthHandler {
|
|||
|
||||
// LoginRequest represents a login request
|
||||
type LoginRequest struct {
|
||||
Code string `json:"code"`
|
||||
Code string `json:"code" validate:"required,min=32,max=128"`
|
||||
}
|
||||
|
||||
// LoginResponse represents a login response
|
||||
|
@ -36,14 +37,19 @@ type LoginResponse struct {
|
|||
OpenID string `json:"openid"`
|
||||
}
|
||||
|
||||
// TokenRequest represents a token verification request
|
||||
type TokenRequest struct {
|
||||
Token string `validate:"required,min=32"`
|
||||
}
|
||||
|
||||
// Login handles WeChat login
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
start := time.Now()
|
||||
|
||||
// Limit request body size to prevent DOS
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1024) // 1KB max for login request
|
||||
|
||||
// Parse request
|
||||
// Parse and validate request
|
||||
var req LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
|
||||
|
@ -52,11 +58,11 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Validate request
|
||||
if req.Code == "" {
|
||||
// Validate using validator
|
||||
if err := api.Validate.Struct(req); err != nil {
|
||||
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
|
||||
"Code is required")
|
||||
log.Printf("Login request validation failed: empty code")
|
||||
fmt.Sprintf("Invalid request parameters: %v", err))
|
||||
log.Printf("Login request validation failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -79,7 +85,7 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// VerifyToken handles token verification
|
||||
func (h *AuthHandler) VerifyToken(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *AuthHandler) VerifyToken(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
start := time.Now()
|
||||
|
||||
// Get token from Authorization header
|
||||
|
@ -100,10 +106,13 @@ func (h *AuthHandler) VerifyToken(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
token := authHeader[7:]
|
||||
if len(token) < 32 { // Basic length check
|
||||
|
||||
// Validate token using validator
|
||||
tokenReq := TokenRequest{Token: token}
|
||||
if err := api.Validate.Struct(tokenReq); err != nil {
|
||||
api.NewResponseWriter(w).WriteErrorWithCode(api.CodeInvalidParams,
|
||||
"Invalid token length")
|
||||
log.Printf("Token verification failed: token too short")
|
||||
"Invalid token format")
|
||||
log.Printf("Token verification failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -139,9 +148,9 @@ func maskToken(token string) string {
|
|||
}
|
||||
|
||||
// Routes returns all routes for the auth handler
|
||||
func (h *AuthHandler) Routes() map[string]http.HandlerFunc {
|
||||
return map[string]http.HandlerFunc{
|
||||
"/login": h.Login,
|
||||
"/verify-token": h.VerifyToken,
|
||||
func (h *AuthHandler) Routes() map[string]httprouter.Handle {
|
||||
return map[string]httprouter.Handle{
|
||||
"/api/login": h.Login,
|
||||
"/api/verify-token": h.VerifyToken,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(¶ms); 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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue