CSRF Protection and Username/Email exists checks

This commit is contained in:
yunginnanet 2020-01-11 13:11:51 -08:00 зафіксовано GitHub
джерело ac9804f7f3
коміт a5f15138dc
Не вдалося знайти GPG ключ що відповідає даному підпису
Ідентифікатор GPG ключа: 4AEE18F83AFDEB23

152
main.go

@ -4,10 +4,14 @@ import (
"database/sql"
"encoding/json"
"fmt"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
"golang.org/x/crypto/bcrypt"
"html/template"
"log"
"net/http"
"regexp"
"strings"
"time"
)
@ -23,11 +27,22 @@ const (
dbname = "maplestory"
)
type Credentials struct {
Password string `json:"password", db:"password"`
Username string `json:"username", db:"username"`
Email string `json:"email", db:"email"`
}
func main() {
http.HandleFunc("/login", Login)
http.HandleFunc("/register", Register)
r := mux.NewRouter()
r.HandleFunc("/", IndexShow)
r.HandleFunc("/register", RegForm)
r.HandleFunc("/login", LoginForm)
r.HandleFunc("/login/submit", Login).Methods("POST")
r.HandleFunc("/register/submit", Register).Methods("POST")
initDB()
log.Fatal(http.ListenAndServe(":42069", nil))
log.Fatal(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r)))
}
func initDB() {
@ -39,21 +54,42 @@ func initDB() {
}
}
type Credentials struct {
Password string `json:"password", db:"password"`
Username string `json:"username", db:"username"`
Email string `json:"email", db:"email"`
Gender int `json:"gender", db:"gender"`
func rowExists(query string, args ...interface{}) bool {
var exists bool
query = fmt.Sprintf("SELECT exists (%s)", query)
err := db.QueryRow(query, args...).Scan(&exists)
if err != nil && err != sql.ErrNoRows {
fmt.Println("Something broke during rowExists() check")
panic(err)
}
return exists
}
func Login(w http.ResponseWriter, r *http.Request) {
func IndexShow(w http.ResponseWriter, r *http.Request) {
ip := strings.Split(r.RemoteAddr, ":")[0]
fmt.Println(ip)
fmt.Fprintf(w, ip)
}
func Register(w http.ResponseWriter, r *http.Request) {
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]
@ -61,18 +97,106 @@ func Register(w http.ResponseWriter, r *http.Request) {
ip = "127.0.0.1"
}
fmt.Println(ip + "/register")
fmt.Println(ip + "/register/submit")
if err != nil {
UsernameInput := r.PostFormValue("username")
PasswordInput := r.PostFormValue("password")
EmailInput := r.PostFormValue("email")
GenderInput := r.PostFormValue("gender")
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
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(creds.Password), 8)
//Validates email addresses and makes sure they are under 254 characters
var rxEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
if len(EmailInput) > 254 || !rxEmail.MatchString(EmailInput) {
fmt.Println("ERROR: that is not a valid email address!")
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) {
fmt.Println("ERROR: Email exists.")
fmt.Fprintf(w, "ERROR: Email exists.")
return
}
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)",
creds.Username, string(hashedPassword), creds.Email, time.Now(), time.Now(), ip, '0', '0', creds.Gender); err != nil {
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) {
creds := &Credentials{}
err := json.NewDecoder(r.Body).Decode(creds)
if err != nil {
// If there is something wrong with the request body, return a 400 status
w.WriteHeader(http.StatusBadRequest)
return
}
// Get the existing entry present in the database for the given username
result := db.QueryRow("select password from users where username=$1", creds.Username)
if err != nil {
// If there is an issue with the database, return a 500 error
w.WriteHeader(http.StatusInternalServerError)
return
}
// 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 {
w.WriteHeader(http.StatusUnauthorized)
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(creds.Password)); 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
}