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") }