package main import ( "crypto/md5" "crypto/subtle" "encoding/hex" "flag" "fmt" "html/template" "log" "net/http" "os" "path/filepath" "strconv" "time" ) type App struct { Store Template *template.Template Domain string } func logMux(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t := time.Now() handler.ServeHTTP(w, r) log.Println(r.Method, r.URL.Path, time.Since(t)) }) } func (app *App) validateHandler(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie("id") //log.Println(r.Header.Get("X-Original-URI")) //log.Println(r.Host) if err != nil { w.WriteHeader(http.StatusUnauthorized) } else { if s, ok := app.GetSession(c.Value); ok { w.Header().Set("X-Remote-User", strconv.FormatInt(s.UserId, 10)) w.WriteHeader(http.StatusOK) } else { log.Println("Session does not exist:", c.Value) w.WriteHeader(http.StatusUnauthorized) } } } func (app *App) loginHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { username := r.FormValue("username") password := r.FormValue("password") next := r.FormValue("next") hash := md5.Sum([]byte(password)) dst := make([]byte, hex.EncodedLen(md5.Size)) hex.Encode(dst, hash[:]) u, ok := app.GetUser(username) if ok && subtle.ConstantTimeCompare(u.Password, dst) == 1 { s := app.NewSession(u.Id) c := http.Cookie{Name: "id", Value: s.Id, Domain: "." + app.Domain} http.SetCookie(w, &c) http.Redirect(w, r, next, http.StatusSeeOther) } else { app.Template.ExecuteTemplate(w, "login.tmpl", struct { Next string }{next}) } } else if r.Method == http.MethodGet { next := r.FormValue("next") app.Template.ExecuteTemplate(w, "login.tmpl", struct { Next string }{next}) } } func (app *App) logoutHandler(w http.ResponseWriter, r *http.Request) { c := http.Cookie{Name: "id", Value: "", Domain: "." + app.Domain, MaxAge: 0} http.SetCookie(w, &c) http.Redirect(w, r, "/login", http.StatusSeeOther) } func main() { flag.Usage = func() { fmt.Fprintf( flag.CommandLine.Output(), "Usage: %s [OPTIONS] [DATABASE] [DOMAIN]\n", os.Args[0], ) flag.PrintDefaults() } address := flag.String("a", ":8080", "bind the server to `address`") templateDir := flag.String("t", "templates", "template `directory`") flag.Parse() if flag.NArg() < 2 { fmt.Print("Argument missing. ") flag.Usage() os.Exit(1) } store := NewPgStore(flag.Args()[0]) template := template.Must(template.New("").ParseGlob(filepath.Join(*templateDir, "*.tmpl"))) app := &App{store, template, flag.Args()[1]} http.HandleFunc("/validate", app.validateHandler) http.HandleFunc("/login", app.loginHandler) http.HandleFunc("/logout", app.logoutHandler) if err := http.ListenAndServe(*address, logMux(http.DefaultServeMux)); err != nil { panic(err) } }