librapi/handlers/login/handler.go
2025-01-03 09:32:27 +01:00

172 lines
4.0 KiB
Go

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()
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 {
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
}
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()
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()
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)
}
}