MapyWeb/main.go
2021-09-19 18:25:05 -07:00

220 lines
6.5 KiB
Go

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!")
}