package main import ( "crypto/subtle" "flag" "fmt" "html/template" "log" "net/http" "os" "path/filepath" "strconv" "time" ) type App struct { Store Template *template.Template Domain string Header 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) validate(r *http.Request) (*Session, bool) { c, err := r.Cookie("id") if err != nil { return nil, false } else if s, ok := app.GetSession(c.Value); ok { return s, true } else { return nil, false } } func (app *App) validateHandler(w http.ResponseWriter, r *http.Request) { if s, ok := app.validate(r); ok { w.Header().Set(app.Header, strconv.FormatInt(s.UserId, 10)) w.WriteHeader(http.StatusOK) } else { w.WriteHeader(http.StatusUnauthorized) } } func (app *App) rootHandler(w http.ResponseWriter, r *http.Request) { if s, ok := app.validate(r); ok { app.Template.ExecuteTemplate(w, "index.tmpl", s) } else { http.Redirect(w, r, "/login", http.StatusSeeOther) } } func (app *App) loginHandler(w http.ResponseWriter, r *http.Request) { if _, ok := app.validate(r); ok { http.Redirect(w, r, "/", http.StatusSeeOther) } else if r.Method == http.MethodPost { username := r.FormValue("username") hash := md5hex([]byte(r.FormValue("password"))) next := r.FormValue("next") u, ok := app.GetUser(username) if ok && subtle.ConstantTimeCompare(u.Password, hash) == 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 { var flash string if !ok { flash = "Utilisateur non enregistré" } else if subtle.ConstantTimeCompare(u.Password, hash) != 1 { flash = "Mot de passe incorrect" } app.Template.ExecuteTemplate(w, "login.tmpl", struct { Next string Flash string }{next, flash}) } } else if r.Method == http.MethodGet { next := r.FormValue("next") app.Template.ExecuteTemplate(w, "login.tmpl", struct { Next string Flash string }{next, ""}) } } type Flash struct { Type string Value string } func (app *App) passwordHandler(w http.ResponseWriter, r *http.Request) { if s, ok := app.validate(r); !ok { http.Redirect(w, r, "/login", http.StatusSeeOther) return } else if r.Method == http.MethodGet { app.Template.ExecuteTemplate(w, "password.tmpl", Flash{}) } else if r.Method == http.MethodPost { password := r.FormValue("password") confirm := r.FormValue("confirm") if password != "" && password == confirm { hash := md5hex([]byte(password)) app.ChangePassword(s.UserId, hash) app.Template.ExecuteTemplate(w, "password.tmpl", Flash{ "success", "Mot de passe enregistré", }) } else { var bad string if password != confirm { bad = "Les deux mots de passe ne coïncident pas" } else if password == "" { bad = "Le mot de passe est vide" } app.Template.ExecuteTemplate(w, "password.tmpl", Flash{ "danger", bad, }) } } } func (app *App) logoutHandler(w http.ResponseWriter, r *http.Request) { if s, ok := app.validate(r); ok { // should we save old sessions in another table? app.DeleteSession(s.Id) c := http.Cookie{ Name: "id", Value: "", Domain: "." + app.Domain, MaxAge: -1, } 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`") header := flag.String( "header", "X-Remote-User", "http `header` containing the user id", ) 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], *header} http.HandleFunc("/validate", app.validateHandler) http.HandleFunc("/login", app.loginHandler) http.HandleFunc("/logout", app.logoutHandler) http.HandleFunc("/", app.rootHandler) http.HandleFunc("/password", app.passwordHandler) if err := http.ListenAndServe(*address, logMux(http.DefaultServeMux)); err != nil { panic(err) } }