diff --git a/README.md b/README.md index 648069c..d04a874 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,15 @@ "UTSA Place" Collaborate website canvas that allows students to place a pixel within a restricted amount of time to make art. +## How to run +1. Install [Go](https://go.dev/dl/) +2. Run the command `go run .` inside this folder +3. View the website at [127.0.0.1:8080](http://127.0.0.1:8080/) + ## Backend Source Our back-end is written in [Go](https://go.dev/) using the standard library. -* [Server](server.go) +* [Request handling](server.go) +* [User registration/login](users.go) ## Frontend Source Our front-end is written in vanilla JavaScript, using the [Bootstrap](https://getbootstrap.com/) diff --git a/ThePlaceHolders b/ThePlaceHolders deleted file mode 100755 index d394606..0000000 Binary files a/ThePlaceHolders and /dev/null differ diff --git a/go.mod b/go.mod index 8442ce1..3494795 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module github.com/adanrsantos/ThePlaceHolders -go 1.23.1 +go 1.23 + +require github.com/gorilla/sessions v1.4.0 + +require github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fe47730 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= diff --git a/server.go b/server.go index 408183e..b75530b 100644 --- a/server.go +++ b/server.go @@ -1,58 +1,51 @@ package main import ( + "crypto/rand" "fmt" + "log" "net/http" + + "github.com/gorilla/sessions" ) const ADDRESS = "127.0.0.1" const PORT = "8080" -type UserForm struct { - Email string - Password string -} - -func extract_user_data(r *http.Request) UserForm { - return UserForm{ - Email: r.FormValue("email"), - Password: r.FormValue("password"), - } -} - -func handle_login(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - return - } - data := extract_user_data(r) - fmt.Fprintln(w, data.Email) - fmt.Fprintln(w, data.Password) -} - -func handle_register(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - return - } - data := extract_user_data(r) - fmt.Fprintln(w, data.Email) - fmt.Fprintln(w, data.Password) +type Server struct { + Users map[string]UserData + Sessions *sessions.CookieStore } func main() { + // Set up encryption for session tokens + fmt.Print("Generating encryption key... ") + secret := make([]byte, 32) + _, err := rand.Read(secret) + if err != nil { + fmt.Println("Error generating key:") + log.Fatal(err) + return + } + fmt.Println("Done!") + // Create server object + server := Server{ + Users: make(map[string]UserData), + Sessions: sessions.NewCookieStore(secret), + } // Host static files static_files := http.FileServer(http.Dir("static/")) http.Handle("/", static_files) - // Response generated by code - http.HandleFunc("/handle-register", handle_register) - http.HandleFunc("/handle-login", handle_login) + http.HandleFunc("/handle-register", server.handle_register) + http.HandleFunc("/handle-login", server.handle_login) // Start web server at 127.0.0.1:8080 + fmt.Printf("Listening to %s on port %s...\n", ADDRESS, PORT) e := http.ListenAndServe(ADDRESS+":"+PORT, nil) // Print any errors if e != nil { - fmt.Println(e) - } else { - fmt.Println("Started server successfully") + fmt.Println("Error starting server:") + log.Fatal(e) } } diff --git a/users.go b/users.go new file mode 100644 index 0000000..95b9f5a --- /dev/null +++ b/users.go @@ -0,0 +1,91 @@ +package main + +import ( + "net/http" + "time" +) + +const SESSION_COOKIE_NAME = "utsa-place-session" +const SESSION_AUTH = "auth" + +type UserData struct { + Email string `json:"email"` + Password string `json:"password"` + AccountCreated time.Time `json:"account-created"` + LastLogin time.Time `json:"last-login"` +} + +func (s *Server) handle_login(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // Get data from form + email := r.FormValue("email") + password := r.FormValue("password") + // Get user from database + user, ok := s.Users[email] + // If user does not exist + if !ok { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // If password does not match + if password != user.Password { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // Generate session + session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) + if err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + session.Values[SESSION_AUTH] = true + if err := session.Save(r, w); err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + // Update last-login on DB + user.LastLogin = time.Now() + s.Users[email] = user + // Redirect to index.html + http.Redirect(w, r, "/", http.StatusFound) +} + +func (s *Server) handle_register(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // Get data from form + email := r.FormValue("email") + password := r.FormValue("password") + // Check that this email is not already registered + if _, ok := s.Users[email]; ok { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // Generate session + session, err := s.Sessions.Get(r, SESSION_COOKIE_NAME) + if err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + // Save user information to DB + s.Users[email] = UserData{ + Email: email, + Password: password, + AccountCreated: time.Now(), + LastLogin: time.Now(), + } + // Make session valid + session.Values[SESSION_AUTH] = true + // Send session token to browser + if err := session.Save(r, w); err != nil { + http.Error(w, "Internal Error", http.StatusInternalServerError) + return + } + // Redirect to index.html + http.Redirect(w, r, "/", http.StatusFound) +} diff --git a/users.json b/users.json new file mode 100644 index 0000000..418dde4 --- /dev/null +++ b/users.json @@ -0,0 +1,3 @@ +{ + "users" = {} +}