mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-27 20:25:41 +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",
|
|
})
|
|
}
|