package login import ( "bytes" "errors" "fmt" "net/http" "github.com/rs/zerolog/log" "librapi/services" "librapi/templates" ) var ( ErrInvalidUsername = errors.New("username must not be empty") ErrInvalidPassword = errors.New("password must not be empty") ErrInvalidCredentials = errors.New("bad credentials") ) type LoginField struct { Name string Value string Err string } type LoginForm struct { Username LoginField Password LoginField Error error Method string } func NewLoginForm() LoginForm { return LoginForm{ Username: LoginField{ Name: "username", }, Password: LoginField{ Name: "password", }, Method: http.MethodPost, } } func (lf *LoginForm) HasErrors() bool { return lf.Username.Err != "" || lf.Password.Err != "" } func (lf *LoginForm) IsSuccess() bool { return lf.Method == http.MethodPost && lf.Error != nil && !lf.HasErrors() } func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: getLogin(w, r, a) case http.MethodPost: postLogin(w, r, a) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } } func extractLoginForm(r *http.Request) LoginForm { lf := NewLoginForm() username := r.FormValue(lf.Username.Name) if username == "" { lf.Username.Err = ErrInvalidUsername.Error() } lf.Username.Value = username password := r.FormValue(lf.Password.Name) if password == "" { lf.Password.Err = ErrInvalidPassword.Error() } lf.Password.Value = password return lf } func postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) { loginForm := templates.GetLoginForm() loginSuccess := templates.GetLoginSuccess() if a.IsLogged(r) { buf := bytes.NewBufferString("") if err := loginSuccess.Execute(buf, nil); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf) return } lf := extractLoginForm(r) if lf.HasErrors() { buf := bytes.NewBufferString("") if err := loginForm.Execute(buf, &lf); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) } w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, buf.String()) return } session, err := a.Authenticate(lf.Username.Value, lf.Password.Value) if err != nil { if errors.Is(err, services.ErrUnauthorized) { log.Warn().Str("username", lf.Username.Value).Msg("bad credentials") lf.Error = ErrInvalidCredentials buf := bytes.NewBufferString("") if err := loginForm.Execute(buf, &lf); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, buf.String()) return } http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } cookie := session.GenerateCookie() http.SetCookie(w, cookie) buf := bytes.NewBufferString("") if err := loginSuccess.Execute(buf, nil); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf) } func getLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) { loginForm := templates.GetLoginForm() if a.IsLogged(r) { loginSuccess := templates.GetLoginSuccess() buf := bytes.NewBufferString("") if err := loginSuccess.Execute(buf, nil); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf) return } buf := bytes.NewBufferString("") if err := loginForm.Execute(buf, &LoginForm{}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf) }