diff options
| -rw-r--r-- | main.go | 64 | ||||
| -rw-r--r-- | store.go | 26 | ||||
| -rw-r--r-- | templates/index.tmpl | 25 | ||||
| -rw-r--r-- | utils.go | 13 |
4 files changed, 98 insertions, 30 deletions
@@ -1,9 +1,7 @@ package main import ( - "crypto/md5" "crypto/subtle" - "encoding/hex" "flag" "fmt" "html/template" @@ -29,35 +27,50 @@ func logMux(handler http.Handler) http.Handler { }) } -func (app *App) validateHandler(w http.ResponseWriter, r *http.Request) { +func (app *App) validate(r *http.Request) (*Session, bool) { c, err := r.Cookie("id") - //log.Println(r.Header.Get("X-Original-URI")) - //log.Println(r.Host) 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("X-Remote-User", 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 { - 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) - } + http.Redirect(w, r, "/login", http.StatusSeeOther) } } func (app *App) loginHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { + if _, ok := app.validate(r); ok { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } else if r.Method == http.MethodPost { username := r.FormValue("username") - password := r.FormValue("password") + hash := md5hex([]byte(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 { + if ok && subtle.ConstantTimeCompare(u.Password, hash) == 1 { s := app.NewSession(u.Id) - c := http.Cookie{Name: "id", Value: s.Id, Domain: "." + app.Domain} + c := http.Cookie{ + Name: "id", + Value: s.Id, + Domain: "." + app.Domain, + } http.SetCookie(w, &c) http.Redirect(w, r, next, http.StatusSeeOther) } else { @@ -74,13 +87,17 @@ func (app *App) loginHandler(w http.ResponseWriter, r *http.Request) { } func (app *App) logoutHandler(w http.ResponseWriter, r *http.Request) { - c := http.Cookie{Name: "id", Value: "", Domain: "." + app.Domain, MaxAge: 0} + 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(), @@ -99,11 +116,14 @@ func main() { } store := NewPgStore(flag.Args()[0]) - template := template.Must(template.New("").ParseGlob(filepath.Join(*templateDir, "*.tmpl"))) + 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) + http.HandleFunc("/", app.rootHandler) if err := http.ListenAndServe(*address, logMux(http.DefaultServeMux)); err != nil { panic(err) } @@ -3,13 +3,15 @@ package main import ( "database/sql" "log" + "time" _ "github.com/lib/pq" ) type Session struct { - Id string - UserId int64 + Id string + UserId int64 + Created time.Time } type User struct { @@ -43,8 +45,11 @@ func (store *PgStore) GetSession(id string) (*Session, bool) { return s, true } s = new(Session) - row := store.QueryRow("SELECT id, user_id FROM sessions WHERE id = $1", id) - if err := row.Scan(&s.Id, &s.UserId); err != nil { + row := store.QueryRow( + "SELECT id, user_id, created_at FROM sessions WHERE id = $1", + id, + ) + if err := row.Scan(&s.Id, &s.UserId, &s.Created); err != nil { return nil, false } store.sessionCache[s.Id] = s @@ -52,16 +57,21 @@ func (store *PgStore) GetSession(id string) (*Session, bool) { } func (store *PgStore) NewSession(userId int64) *Session { - var id string - store.QueryRow("INSERT INTO sessions(user_id) VALUES ($1) RETURNING id", userId).Scan(&id) - s := &Session{id, userId} + s := &Session{UserId: userId} + store.QueryRow( + "INSERT INTO sessions(user_id) VALUES ($1) RETURNING id, created_at", + userId, + ).Scan(&s.Id, &s.Created) store.sessionCache[s.Id] = s return s } func (store *PgStore) GetUser(name string) (*User, bool) { u := &User{Name: name} - row := store.QueryRow("SELECT id, password FROM users WHERE name = $1", name) + row := store.QueryRow( + "SELECT id, password FROM users WHERE name = $1", + name, + ) if err := row.Scan(&u.Id, &u.Password); err != nil { return nil, false } diff --git a/templates/index.tmpl b/templates/index.tmpl new file mode 100644 index 0000000..8f6a3b9 --- /dev/null +++ b/templates/index.tmpl @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <title></title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> +<style> +body{margin: 0 auto; width: 600px; font-family: "Source Sans Pro"; font-size: 15px;} +form > hr{border: none; border-top: 1px solid #e6e6e6; margin: 1.3em 0;} +form > h4{font-weight: 300; font-size: 19px; margin-bottom: 0} label {font-weight: bold; text-align: right;} +form > div {display: grid; grid-template-columns: 2fr 5fr; grid-gap: 1em 1em; align-items: center;} +input, button {font-size: inherit; font-family: inherit; line-height: inherit; padding: 0.8em 1em; border-radius: 0} +input {border: 1px solid #cccccc; transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s} +input:focus {border-color: #66afe9; outline: 0; box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)} +button {border: 1px solid #2780e3; background-color: #2780e3; color: white; cursor: pointer; grid-column: 2; justify-self: left} +button:hover {background-color: #1967be; border-color: #1862b5} +</style> + </head> + <body> + + <p> <a href="/logout">Logout</a></p> + + <p>{{.Id}} {{.Created}}</p> + </body> +</html> diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..c123565 --- /dev/null +++ b/utils.go @@ -0,0 +1,13 @@ +package main + +import ( + "crypto/md5" + "encoding/hex" +) + +func md5hex(b []byte) []byte { + hash := md5.Sum(b) + dst := make([]byte, hex.EncodedLen(md5.Size)) + hex.Encode(dst, hash[:]) + return dst +} |
