2020-01-11 17:56:14 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2020-01-11 21:11:51 +00:00
|
|
|
"html/template"
|
2020-01-11 17:56:14 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2020-01-11 21:11:51 +00:00
|
|
|
"regexp"
|
2020-01-11 17:56:14 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2021-09-02 03:45:16 +00:00
|
|
|
|
|
|
|
"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"
|
2020-01-11 17:56:14 +00:00
|
|
|
)
|
|
|
|
|
2021-09-02 03:45:16 +00:00
|
|
|
func init() {
|
2020-01-11 23:43:22 +00:00
|
|
|
Splash()
|
|
|
|
initDB()
|
|
|
|
}
|
|
|
|
|
2021-09-02 03:45:16 +00:00
|
|
|
func main() {
|
|
|
|
HTTPServ()
|
2020-01-11 23:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func HTTPServ() {
|
2020-01-11 21:11:51 +00:00
|
|
|
r := mux.NewRouter()
|
|
|
|
r.HandleFunc("/", IndexShow)
|
|
|
|
r.HandleFunc("/register", RegForm)
|
|
|
|
r.HandleFunc("/login", LoginForm)
|
2020-01-11 23:43:22 +00:00
|
|
|
r.HandleFunc("/EmailTest", EmailTest)
|
2020-01-11 21:11:51 +00:00
|
|
|
|
2020-01-12 00:34:02 +00:00
|
|
|
//without rate limiting
|
|
|
|
//r.HandleFunc("/login/submit", Login)
|
|
|
|
//r.HandleFunc("/register/submit", Register)
|
|
|
|
|
2020-01-11 23:43:22 +00:00
|
|
|
fmt.Println("Web server starting on port 42069")
|
2020-01-11 21:11:51 +00:00
|
|
|
log.Fatal(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r)))
|
2020-01-11 23:43:22 +00:00
|
|
|
|
2020-01-11 17:56:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
func IndexShow(w http.ResponseWriter, r *http.Request) {
|
2020-01-12 00:34:02 +00:00
|
|
|
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)))
|
2020-01-11 17:56:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
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),
|
|
|
|
})
|
|
|
|
}
|
2020-01-11 17:56:14 +00:00
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
func Register(w http.ResponseWriter, r *http.Request) {
|
2020-01-11 17:56:14 +00:00
|
|
|
creds := &Credentials{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(creds)
|
|
|
|
ip := strings.Split(r.RemoteAddr, ":")[0]
|
|
|
|
if ip == "[" {
|
|
|
|
ip = "127.0.0.1"
|
|
|
|
}
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
fmt.Println(ip + "/register/submit")
|
2020-01-11 17:56:14 +00:00
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
UsernameInput := r.PostFormValue("username")
|
|
|
|
PasswordInput := r.PostFormValue("password")
|
|
|
|
EmailInput := r.PostFormValue("email")
|
|
|
|
GenderInput := r.PostFormValue("gender")
|
|
|
|
|
2020-01-11 23:43:22 +00:00
|
|
|
//debug outputs
|
|
|
|
//fmt.Println(UsernameInput)
|
|
|
|
//fmt.Println(PasswordInput)
|
|
|
|
//fmt.Println(EmailInput)
|
|
|
|
//fmt.Println(GenderInput)
|
2020-01-11 21:11:51 +00:00
|
|
|
|
|
|
|
// 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")
|
2020-01-11 17:56:14 +00:00
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2020-01-11 21:11:51 +00:00
|
|
|
|
|
|
|
//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) {
|
2020-01-11 23:43:22 +00:00
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-01-12 00:34:02 +00:00
|
|
|
//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)
|
|
|
|
}
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
fmt.Println("Passed checks, hashing password with bcrypt...")
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(PasswordInput), 16)
|
2020-01-11 17:56:14 +00:00
|
|
|
|
|
|
|
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)",
|
2020-01-11 21:11:51 +00:00
|
|
|
UsernameInput, string(hashedPassword), EmailInput, time.Now(), time.Now(), ip, 0, 0, GenderInput); err != nil {
|
2020-01-11 17:56:14 +00:00
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
panic(err)
|
|
|
|
return
|
2020-01-11 21:11:51 +00:00
|
|
|
} else {
|
|
|
|
fmt.Println("Success!")
|
|
|
|
http.Redirect(w, r, "/?regsuccess=true", 301)
|
2020-01-11 17:56:14 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-11 21:11:51 +00:00
|
|
|
|
|
|
|
func Login(w http.ResponseWriter, r *http.Request) {
|
2020-01-11 23:43:22 +00:00
|
|
|
UsernameInput := r.PostFormValue("username")
|
|
|
|
PasswordInput := r.PostFormValue("password")
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
// Get the existing entry present in the database for the given username
|
2020-01-11 23:43:22 +00:00
|
|
|
result := db.QueryRow("SELECT password FROM maplestory.accounts WHERE username=$1", UsernameInput)
|
|
|
|
|
2020-01-11 21:11:51 +00:00
|
|
|
// We create another instance of `Credentials` to store the credentials we get from the database
|
|
|
|
storedCreds := &Credentials{}
|
|
|
|
// Store the obtained password in `storedCreds`
|
2020-01-11 23:43:22 +00:00
|
|
|
err := result.Scan(&storedCreds.Password)
|
2020-01-11 21:11:51 +00:00
|
|
|
if err != nil {
|
|
|
|
// If an entry with the username does not exist, send an "Unauthorized"(401) status
|
|
|
|
if err == sql.ErrNoRows {
|
2020-01-11 23:43:22 +00:00
|
|
|
fmt.Println("Login failed!")
|
|
|
|
fmt.Fprintf(w, "Login failed!")
|
2020-01-11 21:11:51 +00:00
|
|
|
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
|
2020-01-11 23:43:22 +00:00
|
|
|
if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(PasswordInput)); err != nil {
|
2020-01-11 21:11:51 +00:00
|
|
|
// 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
|
2020-01-11 23:43:22 +00:00
|
|
|
|
2020-01-12 00:34:02 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-01-11 23:43:22 +00:00
|
|
|
fmt.Println("Login successful!")
|
|
|
|
fmt.Fprintf(w, "Login successful!")
|
2020-01-11 21:11:51 +00:00
|
|
|
}
|