opengist/internal/web/handlers/auth/password.go
Thomas Miceli c2899ef41b wip
2025-01-11 20:17:01 +01:00

169 lines
4.7 KiB
Go

package auth
import (
"errors"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/db"
"github.com/thomiceli/opengist/internal/i18n"
"github.com/thomiceli/opengist/internal/utils"
"github.com/thomiceli/opengist/internal/web/context"
"gorm.io/gorm"
)
func Register(ctx *context.Context) error {
disableSignup := ctx.GetData("DisableSignup")
disableForm := ctx.GetData("DisableLoginForm")
code := ctx.QueryParam("code")
if code != "" {
if invitation, err := db.GetInvitationByCode(code); err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return ctx.ErrorRes(500, "Cannot check for invitation code", err)
} else if invitation != nil && invitation.IsUsable() {
disableSignup = false
}
}
ctx.SetData("title", ctx.TrH("auth.new-account"))
ctx.SetData("htmlTitle", ctx.TrH("auth.new-account"))
ctx.SetData("disableForm", disableForm)
ctx.SetData("disableSignup", disableSignup)
ctx.SetData("isLoginPage", false)
return ctx.HTML_("auth_form.html")
}
func ProcessRegister(ctx *context.Context) error {
disableSignup := ctx.GetData("DisableSignup")
code := ctx.QueryParam("code")
invitation, err := db.GetInvitationByCode(code)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return ctx.ErrorRes(500, "Cannot check for invitation code", err)
} else if invitation.ID != 0 && invitation.IsUsable() {
disableSignup = false
}
if disableSignup == true {
return ctx.ErrorRes(403, ctx.Tr("error.signup-disabled"), nil)
}
if ctx.GetData("DisableLoginForm") == true {
return ctx.ErrorRes(403, ctx.Tr("error.signup-disabled-form"), nil)
}
ctx.SetData("title", ctx.TrH("auth.new-account"))
ctx.SetData("htmlTitle", ctx.TrH("auth.new-account"))
sess := ctx.GetSession()
dto := new(db.UserDTO)
if err := ctx.Bind(dto); err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
}
if err := ctx.Validate(dto); err != nil {
ctx.AddFlash(utils.ValidationMessages(&err, ctx.GetData("locale").(*i18n.Locale)), "error")
return ctx.HTML_("auth_form.html")
}
if exists, err := db.UserExists(dto.Username); err != nil || exists {
ctx.AddFlash(ctx.Tr("flash.auth.username-exists"), "error")
return ctx.HTML_("auth_form.html")
}
user := dto.ToUser()
password, err := utils.Argon2id.Hash(user.Password)
if err != nil {
return ctx.ErrorRes(500, "Cannot hash password", err)
}
user.Password = password
if err = user.Create(); err != nil {
return ctx.ErrorRes(500, "Cannot create user", err)
}
if user.ID == 1 {
if err = user.SetAdmin(); err != nil {
return ctx.ErrorRes(500, "Cannot set user admin", err)
}
}
if invitation.ID != 0 {
if err := invitation.Use(); err != nil {
return ctx.ErrorRes(500, "Cannot use invitation", err)
}
}
sess.Values["user"] = user.ID
ctx.SaveSession(sess)
return ctx.RedirectTo("/")
}
func Login(ctx *context.Context) error {
ctx.SetData("title", ctx.TrH("auth.login"))
ctx.SetData("htmlTitle", ctx.TrH("auth.login"))
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
ctx.SetData("isLoginPage", true)
return ctx.HTML_("auth_form.html")
}
func ProcessLogin(ctx *context.Context) error {
if ctx.GetData("DisableLoginForm") == true {
return ctx.ErrorRes(403, ctx.Tr("error.login-disabled-form"), nil)
}
var err error
sess := ctx.GetSession()
dto := &db.UserDTO{}
if err = ctx.Bind(dto); err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
}
password := dto.Password
var user *db.User
if user, err = db.GetUserByUsername(dto.Username); err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return ctx.ErrorRes(500, "Cannot get user", err)
}
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
ctx.AddFlash(ctx.Tr("flash.auth.invalid-credentials"), "error")
return ctx.RedirectTo("/login")
}
if ok, err := utils.Argon2id.Verify(password, user.Password); !ok {
if err != nil {
return ctx.ErrorRes(500, "Cannot check for password", err)
}
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
ctx.AddFlash(ctx.Tr("flash.auth.invalid-credentials"), "error")
return ctx.RedirectTo("/login")
}
// handle MFA
var hasWebauthn, hasTotp bool
if hasWebauthn, hasTotp, err = user.HasMFA(); err != nil {
return ctx.ErrorRes(500, "Cannot check for user MFA", err)
}
if hasWebauthn || hasTotp {
sess.Values["mfaID"] = user.ID
sess.Options.MaxAge = 5 * 60 // 5 minutes
ctx.SaveSession(sess)
return ctx.RedirectTo("/mfa")
}
sess.Values["user"] = user.ID
sess.Options.MaxAge = 60 * 60 * 24 * 365 // 1 year
ctx.SaveSession(sess)
ctx.DeleteCsrfCookie()
return ctx.RedirectTo("/")
}
func Logout(ctx *context.Context) error {
ctx.DeleteSession()
ctx.DeleteCsrfCookie()
return ctx.RedirectTo("/all")
}