MapyWeb/main.go

230 lines
5.9 KiB
Go

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