package main import ( "database/sql" "encoding/json" "fmt" "html/template" "log" "net/http" "regexp" "strings" "time" "github.com/badoux/checkmail" "github.com/gorilla/csrf" "github.com/gorilla/mux" _ "github.com/lib/pq" "github.com/satori/go.uuid" "golang.org/x/crypto/bcrypt" ) func init() { Splash() initDB() } func main() { HTTPServ() } func HTTPServ() { r := mux.NewRouter() r.HandleFunc("/", IndexShow) r.HandleFunc("/register", RegForm) r.HandleFunc("/login", LoginForm) r.HandleFunc("/EmailTest", EmailTest) //without rate limiting //r.HandleFunc("/login/submit", Login) //r.HandleFunc("/register/submit", Register) fmt.Println("Web server starting on port 42069") log.Fatal(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r))) } func IndexShow(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 w.Write([]byte(fmt.Sprintf("Welcome %s!", response))) } func LoginForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) t, _ := template.ParseFiles("login.tmpl") t.ExecuteTemplate(w, "login.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), }) } func RegForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) t, _ := template.ParseFiles("register.tmpl") t.ExecuteTemplate(w, "register.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), }) } func Register(w http.ResponseWriter, r *http.Request) { creds := &Credentials{} err := json.NewDecoder(r.Body).Decode(creds) ip := strings.Split(r.RemoteAddr, ":")[0] if ip == "[" { ip = "127.0.0.1" } fmt.Println(ip + "/register/submit") 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) // Verify Gender value is either 0(female) or 1(male) // if not just silently send them a 400 because wtf if GenderInput != "0" && GenderInput != "1" { fmt.Println("Gender Input BAD") w.WriteHeader(http.StatusBadRequest) return } //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 rowExists("Select id from maplestory.accounts where username=$1", UsernameInput) { fmt.Println("ERROR: Username exists.") fmt.Fprintf(w, "ERROR: Username exists.") return } //Check if email is taken if rowExists("Select id from maplestory.accounts where email=$1", EmailInput) { return } //Validates email addresses err = checkmail.ValidateFormat(EmailInput) if err != nil { fmt.Println("ERROR: that is not a valid email address!") fmt.Fprintf(w, "ERROR: that is not a valid email address!") //return panic(err) } fmt.Println("Passed checks, hashing password with bcrypt...") hashedPassword, err := bcrypt.GenerateFromPassword([]byte(PasswordInput), 16) if _, err = db.Query("INSERT INTO maplestory.accounts (username,password,email,creation,last_login,last_ip,ban,admin,gender) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)", UsernameInput, string(hashedPassword), EmailInput, time.Now(), time.Now(), ip, 0, 0, GenderInput); err != nil { w.WriteHeader(http.StatusInternalServerError) panic(err) return } else { fmt.Println("Success!") http.Redirect(w, r, "/?regsuccess=true", 301) } } func Login(w http.ResponseWriter, r *http.Request) { UsernameInput := r.PostFormValue("username") PasswordInput := r.PostFormValue("password") // Get the existing entry present in the database for the given username result := db.QueryRow("SELECT password FROM maplestory.accounts WHERE username=$1", UsernameInput) // We create another instance of `Credentials` to store the credentials we get from the database storedCreds := &Credentials{} // Store the obtained password in `storedCreds` err := result.Scan(&storedCreds.Password) if err != nil { // If an entry with the username does not exist, send an "Unauthorized"(401) status if err == sql.ErrNoRows { fmt.Println("Login failed!") fmt.Fprintf(w, "Login failed!") return } // If the error is of any other type, send a 500 status w.WriteHeader(http.StatusInternalServerError) return } // Compare the stored hashed password, with the hashed version of the password that was received if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(PasswordInput)); err != nil { // If the two passwords don't match, return a 401 status w.WriteHeader(http.StatusUnauthorized) } // If we reach this point, that means the users password was correct, and that they are authorized // The default 200 status is sent 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("Login successful!") fmt.Fprintf(w, "Login successful!") }