package login import ( "bytes" "errors" "fmt" "librapi/handlers/login/templates" "librapi/services" "net/http" "os" "sync" "github.com/rs/zerolog/log" ) var ( adminUsername = sync.OnceValue[string](func() string { return os.Getenv("API_ADMIN_USERNAME") }) adminPassword = sync.OnceValue[string](func() string { return os.Getenv("API_ADMIN_PASSWORD") }) ) 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 (lf *LoginForm) ValidCredentials() bool { return lf.Username.Value == adminUsername() && lf.Password.Value == adminPassword() } func Handler(s *services.SessionStore) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: getLogin(w, r, s) case http.MethodPost: postLogin(w, r, s) 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, s *services.SessionStore) { loginForm := templates.GetLoginForm() if loginForm == nil { log.Error().Msg("unable to load login form") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) 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 } if ok := lf.ValidCredentials(); !ok { 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 } session, err := s.NewSession() if err != nil { log.Err(err).Msg("unable to create a new session") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } cookie := session.GenerateCookie() http.SetCookie(w, cookie) loginSuccess := templates.GetLoginSuccess() if loginSuccess == nil { log.Error().Msg("unable to load login success") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, loginSuccess.Tree.Root.String()) } func getLogin(w http.ResponseWriter, r *http.Request, s *services.SessionStore) { loginForm := templates.GetLoginForm() if loginForm == nil { log.Error().Msg("unable to load login form") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } if s.IsLogged(r) { loginSuccess := templates.GetLoginSuccess() if loginSuccess == nil { log.Error().Msg("unable to load login success") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, loginSuccess.Tree.Root.String()) 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 } if _, err := fmt.Fprint(w, buf); err != nil { log.Err(err).Msg("unable to write to response") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) } }