mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package handlers
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"tinyauth/internal/types"
 | 
						|
	"tinyauth/internal/utils"
 | 
						|
 | 
						|
	"github.com/gin-gonic/gin"
 | 
						|
	"github.com/pquerna/otp/totp"
 | 
						|
	"github.com/rs/zerolog/log"
 | 
						|
)
 | 
						|
 | 
						|
func (h *Handlers) LoginHandler(c *gin.Context) {
 | 
						|
	var login types.LoginRequest
 | 
						|
 | 
						|
	err := c.BindJSON(&login)
 | 
						|
	if err != nil {
 | 
						|
		log.Error().Err(err).Msg("Failed to bind JSON")
 | 
						|
		c.JSON(400, gin.H{
 | 
						|
			"status":  400,
 | 
						|
			"message": "Bad Request",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Debug().Msg("Got login request")
 | 
						|
 | 
						|
	clientIP := c.ClientIP()
 | 
						|
 | 
						|
	// Create an identifier for rate limiting (username or IP if username doesn't exist yet)
 | 
						|
	rateIdentifier := login.Username
 | 
						|
	if rateIdentifier == "" {
 | 
						|
		rateIdentifier = clientIP
 | 
						|
	}
 | 
						|
 | 
						|
	// Check if the account is locked due to too many failed attempts
 | 
						|
	locked, remainingTime := h.Auth.IsAccountLocked(rateIdentifier)
 | 
						|
	if locked {
 | 
						|
		log.Warn().Str("identifier", rateIdentifier).Int("remaining_seconds", remainingTime).Msg("Account is locked due to too many failed login attempts")
 | 
						|
		c.JSON(429, gin.H{
 | 
						|
			"status":  429,
 | 
						|
			"message": fmt.Sprintf("Too many failed login attempts. Try again in %d seconds", remainingTime),
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Search for a user based on username
 | 
						|
	log.Debug().Interface("username", login.Username).Msg("Searching for user")
 | 
						|
 | 
						|
	userSearch := h.Auth.SearchUser(login.Username)
 | 
						|
 | 
						|
	// User does not exist
 | 
						|
	if userSearch.Type == "" {
 | 
						|
		log.Debug().Str("username", login.Username).Msg("User not found")
 | 
						|
		// Record failed login attempt
 | 
						|
		h.Auth.RecordLoginAttempt(rateIdentifier, false)
 | 
						|
		c.JSON(401, gin.H{
 | 
						|
			"status":  401,
 | 
						|
			"message": "Unauthorized",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Debug().Msg("Got user")
 | 
						|
 | 
						|
	// Check if password is correct
 | 
						|
	if !h.Auth.VerifyUser(userSearch, login.Password) {
 | 
						|
		log.Debug().Str("username", login.Username).Msg("Password incorrect")
 | 
						|
		// Record failed login attempt
 | 
						|
		h.Auth.RecordLoginAttempt(rateIdentifier, false)
 | 
						|
		c.JSON(401, gin.H{
 | 
						|
			"status":  401,
 | 
						|
			"message": "Unauthorized",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Debug().Msg("Password correct, checking totp")
 | 
						|
 | 
						|
	// Record successful login attempt (will reset failed attempt counter)
 | 
						|
	h.Auth.RecordLoginAttempt(rateIdentifier, true)
 | 
						|
 | 
						|
	// Check if user is using TOTP
 | 
						|
	if userSearch.Type == "local" {
 | 
						|
		// Get local user
 | 
						|
		localUser := h.Auth.GetLocalUser(login.Username)
 | 
						|
 | 
						|
		// Check if TOTP is enabled
 | 
						|
		if localUser.TotpSecret != "" {
 | 
						|
			log.Debug().Msg("Totp enabled")
 | 
						|
 | 
						|
			// Set totp pending cookie
 | 
						|
			h.Auth.CreateSessionCookie(c, &types.SessionCookie{
 | 
						|
				Username:    login.Username,
 | 
						|
				Name:        utils.Capitalize(login.Username),
 | 
						|
				Email:       fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
 | 
						|
				Provider:    "username",
 | 
						|
				TotpPending: true,
 | 
						|
			})
 | 
						|
 | 
						|
			// Return totp required
 | 
						|
			c.JSON(200, gin.H{
 | 
						|
				"status":      200,
 | 
						|
				"message":     "Waiting for totp",
 | 
						|
				"totpPending": true,
 | 
						|
			})
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Create session cookie with username as provider
 | 
						|
	h.Auth.CreateSessionCookie(c, &types.SessionCookie{
 | 
						|
		Username: login.Username,
 | 
						|
		Name:     utils.Capitalize(login.Username),
 | 
						|
		Email:    fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
 | 
						|
		Provider: "username",
 | 
						|
	})
 | 
						|
 | 
						|
	// Return logged in
 | 
						|
	c.JSON(200, gin.H{
 | 
						|
		"status":      200,
 | 
						|
		"message":     "Logged in",
 | 
						|
		"totpPending": false,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func (h *Handlers) TOTPHandler(c *gin.Context) {
 | 
						|
	var totpReq types.TotpRequest
 | 
						|
 | 
						|
	err := c.BindJSON(&totpReq)
 | 
						|
	if err != nil {
 | 
						|
		log.Error().Err(err).Msg("Failed to bind JSON")
 | 
						|
		c.JSON(400, gin.H{
 | 
						|
			"status":  400,
 | 
						|
			"message": "Bad Request",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Debug().Msg("Checking totp")
 | 
						|
 | 
						|
	// Get user context
 | 
						|
	userContext := h.Hooks.UseUserContext(c)
 | 
						|
 | 
						|
	// Check if we have a user
 | 
						|
	if userContext.Username == "" {
 | 
						|
		log.Debug().Msg("No user context")
 | 
						|
		c.JSON(401, gin.H{
 | 
						|
			"status":  401,
 | 
						|
			"message": "Unauthorized",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Get user
 | 
						|
	user := h.Auth.GetLocalUser(userContext.Username)
 | 
						|
 | 
						|
	// Check if totp is correct
 | 
						|
	ok := totp.Validate(totpReq.Code, user.TotpSecret)
 | 
						|
 | 
						|
	if !ok {
 | 
						|
		log.Debug().Msg("Totp incorrect")
 | 
						|
		c.JSON(401, gin.H{
 | 
						|
			"status":  401,
 | 
						|
			"message": "Unauthorized",
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Debug().Msg("Totp correct")
 | 
						|
 | 
						|
	// Create session cookie with username as provider
 | 
						|
	h.Auth.CreateSessionCookie(c, &types.SessionCookie{
 | 
						|
		Username: user.Username,
 | 
						|
		Name:     utils.Capitalize(user.Username),
 | 
						|
		Email:    fmt.Sprintf("%s@%s", strings.ToLower(user.Username), h.Config.Domain),
 | 
						|
		Provider: "username",
 | 
						|
	})
 | 
						|
 | 
						|
	// Return logged in
 | 
						|
	c.JSON(200, gin.H{
 | 
						|
		"status":  200,
 | 
						|
		"message": "Logged in",
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func (h *Handlers) LogoutHandler(c *gin.Context) {
 | 
						|
	log.Debug().Msg("Cleaning up redirect cookie")
 | 
						|
 | 
						|
	h.Auth.DeleteSessionCookie(c)
 | 
						|
 | 
						|
	c.JSON(200, gin.H{
 | 
						|
		"status":  200,
 | 
						|
		"message": "Logged out",
 | 
						|
	})
 | 
						|
}
 |