package main import ( "crypto/rand" "database/sql" "encoding/base64" "sync" _ "github.com/lib/pq" "fmt" "net/http" ) type UserSession struct { AccountIDX int Username string } type PageData struct { session *UserSession } var sessionsMutex sync.Mutex var sessions = map[string]*UserSession{} var db *sql.DB func SendError(w http.ResponseWriter, r *http.Request, code int) { w.WriteHeader(code) errorString := fmt.Sprintf("Error %d", code) page := tPage(tError(errorString)) page.Render(r.Context(), w) } func AttemptCookieAuth(req *http.Request) *UserSession { cookie, err := req.Cookie("session") if err != nil { return nil } sessionsMutex.Lock() session := sessions[cookie.Value] sessionsMutex.Unlock() return session } func RootHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { SendError(w, r, http.StatusBadRequest) return } session := AttemptCookieAuth(r) if session != nil { page := tPage(tHello(session)) page.Render(r.Context(), w) return } else { page := tPage(tLoginForm("")) page.Render(r.Context(), w) return } } func LoginHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { SendError(w, r, http.StatusBadRequest) return } err := r.ParseForm() if err != nil { SendError(w, r, http.StatusBadRequest) return } session := AttemptCookieAuth(r) if session != nil { http.Redirect(w, r, "/", http.StatusSeeOther) return } username := r.FormValue("username") password := r.FormValue("password") loginResult, err := DBLogin(username, password) if err != nil { loginForm := tLoginForm("Invalid username or password") page := tPage(loginForm) page.Render(r.Context(), w) return } var cookieBytes [24]byte rand.Read(cookieBytes[:]) cookie := base64.URLEncoding.EncodeToString(cookieBytes[:]) session = &UserSession{ AccountIDX: loginResult.Idx, Username: username, } sessionsMutex.Lock() sessions[cookie] = session sessionsMutex.Unlock() http.SetCookie(w, &http.Cookie{ Name: "session", Value: cookie, HttpOnly: true, Path: "/", }) pageContents := tHello(session) page := tPage(pageContents) page.Render(r.Context(), w) } func main() { var err error conninfo := "host=localhost user=postgres password=work dbname=rpg sslmode=disable" listenEndpoint := "localhost:8000" db, err = sql.Open("postgres", conninfo) if err != nil { panic(err) } defer db.Close() err = db.Ping() if err != nil { panic(err) } fmt.Println("Connected to database") http.HandleFunc("/login", LoginHandler) http.HandleFunc("/", RootHandler) fmt.Printf("Listening on %s\n", listenEndpoint) http.ListenAndServe(listenEndpoint, nil) }