All checks were successful
Release code / build (push) Successful in 11m41s
This fixes database error reporting, as well as majorly fixes users not being able to authenticate to the API if you used the automated migration setup, as the password would remain in hex encoding. We now decode the hexadecimal and then change it to the far more compact base64 encoding before adding it to the database. This should fix login, and not cause 500 Interal Server Errors anymore. Sorry folks!
157 lines
3.3 KiB
Go
157 lines
3.3 KiB
Go
package users
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
|
"github.com/charmbracelet/log"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type UserLoginRequest struct {
|
|
Username *string
|
|
Email *string
|
|
|
|
Password string `validate:"required"`
|
|
}
|
|
|
|
func LoginUser(c *gin.Context) {
|
|
var req UserLoginRequest
|
|
|
|
if err := c.BindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": fmt.Sprintf("Failed to parse body: %s", err.Error()),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if err := validator.New().Struct(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if req.Email == nil && req.Username == nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Missing both email and username in body",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
userFindRequestArguments := make([]interface{}, 1)
|
|
userFindRequest := ""
|
|
|
|
if req.Email != nil {
|
|
userFindRequestArguments[0] = &req.Email
|
|
userFindRequest += "email = ?"
|
|
}
|
|
|
|
if req.Username != nil {
|
|
userFindRequestArguments[0] = &req.Username
|
|
userFindRequest += "username = ?"
|
|
}
|
|
|
|
var user *dbcore.User
|
|
userRequest := dbcore.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
|
|
|
if userRequest.Error != nil {
|
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to find if user exists",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
userExists := userRequest.RowsAffected > 0
|
|
|
|
if !userExists {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "User not found",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
decodedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(user.Password)))
|
|
_, err := base64.StdEncoding.Decode(decodedPassword, []byte(user.Password))
|
|
|
|
if err != nil {
|
|
log.Warnf("failed to decode password in database: %s", err.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to parse database result for password",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
err = bcrypt.CompareHashAndPassword(decodedPassword, []byte(req.Password))
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "Invalid password",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
tokenRandomData := make([]byte, 80)
|
|
|
|
if _, err := rand.Read(tokenRandomData); err != nil {
|
|
log.Warnf("Failed to read random data to use as token: %s", err.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to generate refresh token",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
token := &dbcore.Token{
|
|
UserID: user.ID,
|
|
|
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
|
DisableExpiry: forceNoExpiryTokens,
|
|
CreationIPAddr: c.ClientIP(),
|
|
}
|
|
|
|
if result := dbcore.DB.Create(&token); result.Error != nil {
|
|
log.Warnf("Failed to create user: %s", result.Error.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to add refresh token into database",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
jwt, err := jwtcore.Generate(user.ID)
|
|
|
|
if err != nil {
|
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to generate refresh token",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"token": jwt,
|
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
|
})
|
|
}
|