package main import ( "fmt" "html/template" "net" "net/http" "regexp" "strconv" "strings" "time" "github.com/valyala/fasthttp" "github.com/rs/zerolog/log" "github.com/badoux/checkmail" "github.com/gorilla/csrf" "github.com/gorilla/mux" _ "github.com/lib/pq" "github.com/satori/go.uuid" "mapyweb/db" "mapyweb/extra" ) func init() { extra.Banner() db.StartDatabases() } func main() { HTTPServ() } type authError string const ( usernameExists authError = "username is taken" emailExists authError = "email is taken" rateLimited authError = "you are being ratelimited, slow down" badEmail authError = "not a valid email address" internalErr authError = "internal server error" invalidRequest authError = "invalid request" badCreds authError = "invalid credentials" ) func HTTPServ() { r := mux.NewRouter() r.HandleFunc("/", showIndex) r.HandleFunc("/register", registrationForm) r.HandleFunc("/login", loginForm) r.HandleFunc("/EmailTest", EmailTest) fmt.Println("Web server starting on port 42069") log.Fatal().Err(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r))). Msg("http failure") } func logAndReturnError(w http.ResponseWriter, e authError) { log.Error().Str("error", string(e)).Msg("auth_fail") if _, err := fmt.Fprint(w, e); err != nil { log.Debug().Err(err).Msg("failed to write to ResponseWriter") } } func fprinter(w http.ResponseWriter, s string, a ...interface{}) { if _, err := fmt.Fprint(w, s, a); err != nil { log.Debug().Err(err).Msg("failed to write to ResponseWriter") } } func showIndex(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie("session_token") if err != nil { if err == http.ErrNoCookie { // If the cookie is not set, return an unauthorized status w.WriteHeader(http.StatusUnauthorized) return } // For any other type of error, return a bad request status w.WriteHeader(http.StatusBadRequest) return } sessionToken := c.Value // We then get the name of the user from our cache, where we set the session token response, err := cache.Do("GET", sessionToken) if err != nil { // If there is an error fetching from cache, return an internal server error status w.WriteHeader(http.StatusInternalServerError) return } if response == nil { // If the session token is not present in cache, redirect to login screen http.Redirect(w, r, "/login", http.StatusSeeOther) return } // Finally, return the welcome message to the user fprinter(w, "Welcome %s!", response) } func loginForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) t, err := template.ParseFiles("login.tmpl") if err != nil { panic(err) } if err := t.ExecuteTemplate(w, "login.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), }); err != nil { panic(err) } } func registrationForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) t, err := template.ParseFiles("register.tmpl") if err != nil { panic(err) } if err := t.ExecuteTemplate(w, "register.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), }); err != nil { panic(err) } } func register(w http.ResponseWriter, r *http.Request) { var ip string var err error ip, _, err = net.SplitHostPort(r.RemoteAddr) if err != nil { panic(err) } log.Debug().Str("ip", ip).Msg("register") usernameInput := r.PostFormValue("username") passwordInput := r.PostFormValue("password") emailInput := r.PostFormValue("email") genderInput := r.PostFormValue("gender") // debug outputs // fmt.Println(usernameInput) // fmt.Println(passwordInput) // fmt.Println(emailInput) // fmt.Println(genderInput) var gender int // Verify Gender value is either 0(female) or 1(male) // if not just silently send them a 400 because wtf if gender, err = strconv.Atoi(genderInput); err != nil || gender > 1 { logAndReturnError(w, invalidRequest) } // Usernames must only be letters, numbers, dashes, and underscores var rxUsername = regexp.MustCompile("([\\w\\-]+)") // Usernames must be under 16 characters if len(usernameInput) > 16 || !rxUsername.MatchString(usernameInput) { fmt.Println("ERROR: Username must only be alphanumeric and under 16 characters. \"-\" and \"_\" are also accepted.") fmt.Println(w, "ERROR: Username must only be alphanumeric and under 16 characters. \"-\" and \"_\" are also accepted.") return } // Check if username is taken if db.UserExists(usernameInput) { logAndReturnError(w, usernameExists) return } // Check if email is taken if db.EmailTaken(emailInput) { logAndReturnError(w, emailExists) return } // Validates email addresses err = checkmail.ValidateFormat(emailInput) if err != nil { logAndReturnError(w, badEmail) return } err = db.RegisterNewUser(usernameInput, passwordInput, r.RemoteAddr, genderInput, false) if err != nil { logAndReturnError(w, internalErr) return } fmt.Println("Success!") http.Redirect(w, r, "/?regsuccess=true", 301) } func newLogin(w http.ResponseWriter, r *http.Request) { var u *db.User var err error usernameInput := r.PostFormValue("username") passwordInput := r.PostFormValue("password") if u, err = db.AttemptWebLogin(usernameInput, passwordInput, r.RemoteAddr, r.UserAgent()); err != nil { logAndReturnError(w, badCreds) } fmt.Println("Authentication successful, setting session...") // sets a 2 hour long session and cookie sessionToken := uuid.NewV4().String() _, err = cache.Do("SETEX", sessionToken, "7200", usernameInput) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Println("Session management failed!") http.SetCookie(w, &http.Cookie{ Name: "session_token", Value: sessionToken, Expires: time.Now().Add(7200 * time.Second), }) return } fmt.Println("newLogin successful!") fprinter(w, "newLogin successful!") }