119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
package web
|
||
|
||
import (
|
||
"net/http"
|
||
"net/url"
|
||
"regexp"
|
||
"strings"
|
||
"unicode/utf8"
|
||
|
||
"golang.org/x/crypto/bcrypt"
|
||
|
||
"plumber/internal/store"
|
||
)
|
||
|
||
var usernameRe = regexp.MustCompile(`^[a-zA-Z0-9_]{3,20}$`)
|
||
|
||
func safeNext(raw string) string {
|
||
if raw == "" {
|
||
return "/"
|
||
}
|
||
u, err := url.Parse(raw)
|
||
if err != nil || u.IsAbs() || !strings.HasPrefix(u.Path, "/") || strings.HasPrefix(u.Path, "//") {
|
||
return "/"
|
||
}
|
||
return u.RequestURI()
|
||
}
|
||
|
||
func (s *Server) handleLoginForm(w http.ResponseWriter, r *http.Request) {
|
||
if currentUser(r) != nil {
|
||
http.Redirect(w, r, safeNext(r.URL.Query().Get("next")), http.StatusSeeOther)
|
||
return
|
||
}
|
||
s.exec(w, "login", authPage{
|
||
page: s.basePage(r, "Sign in"),
|
||
Next: r.URL.Query().Get("next"),
|
||
})
|
||
}
|
||
|
||
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||
if !s.requireCSRF(w, r) {
|
||
return
|
||
}
|
||
username := strings.TrimSpace(r.PostFormValue("username"))
|
||
password := r.PostFormValue("password")
|
||
next := safeNext(r.PostFormValue("next"))
|
||
u, err := s.store.UserByUsername(r.Context(), username)
|
||
if err != nil || bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)) != nil {
|
||
w.WriteHeader(http.StatusUnauthorized)
|
||
s.exec(w, "login", authPage{
|
||
page: s.basePage(r, "Sign in"),
|
||
Username: username,
|
||
Next: next,
|
||
Error: "Wrong username or password.",
|
||
})
|
||
return
|
||
}
|
||
s.sessions.Put(r.Context(), "user_id", u.ID)
|
||
http.Redirect(w, r, next, http.StatusSeeOther)
|
||
}
|
||
|
||
func (s *Server) handleRegisterForm(w http.ResponseWriter, r *http.Request) {
|
||
if currentUser(r) != nil {
|
||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||
return
|
||
}
|
||
s.exec(w, "register", authPage{page: s.basePage(r, "Create account")})
|
||
}
|
||
|
||
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||
if !s.requireCSRF(w, r) {
|
||
return
|
||
}
|
||
username := strings.TrimSpace(r.PostFormValue("username"))
|
||
password := r.PostFormValue("password")
|
||
p := authPage{page: s.basePage(r, "Create account"), Username: username}
|
||
if !usernameRe.MatchString(username) {
|
||
p.Error = "Username must be 3–20 letters, numbers, or underscores."
|
||
s.exec(w, "register", p)
|
||
return
|
||
}
|
||
if utf8.RuneCountInString(password) < 8 {
|
||
p.Error = "Password must be at least 8 characters."
|
||
s.exec(w, "register", p)
|
||
return
|
||
}
|
||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
http.Error(w, "could not save password", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
role := store.RoleUser
|
||
if s.cfg.AdminUsername != "" && store.NormalizeUsername(username) == store.NormalizeUsername(s.cfg.AdminUsername) {
|
||
n, err := s.store.CountAdmins(r.Context())
|
||
if err != nil {
|
||
http.Error(w, "could not create account", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
if n == 0 {
|
||
role = store.RoleAdmin
|
||
}
|
||
}
|
||
u, err := s.store.CreateUser(r.Context(), store.NewUser{
|
||
Username: username,
|
||
PasswordHash: string(hash),
|
||
Role: role,
|
||
})
|
||
if err != nil {
|
||
p.Error = "That username is taken."
|
||
s.exec(w, "register", p)
|
||
return
|
||
}
|
||
s.sessions.Put(r.Context(), "user_id", u.ID)
|
||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||
}
|
||
|
||
func (s *Server) handleAuthPrompt(w http.ResponseWriter, r *http.Request) {
|
||
s.exec(w, "signin-prompt", s.basePage(r, ""))
|
||
}
|