package admin import ( "crypto/hmac" "crypto/sha256" "errors" "fmt" "html/template" "io/ioutil" "log" "net/http" "os" "path/filepath" "time" "github.com/gorilla/csrf" "github.com/gorilla/sessions" ) const ( USER = "username" PWD = "password" SESS_USER = "username" SESSION = "photoblog.session" ) // Application type AuthCookie struct { Templates *template.Template Store *sessions.CookieStore DataDir *os.File PasswordSecret string CsrfSecret string } // Constructor AuthCookie func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret, csrfSecret string, data *os.File) *AuthCookie { app := &AuthCookie{ Templates: tpl, Store: sessions.NewCookieStore([]byte(sessionSecret)), DataDir: data, PasswordSecret: passwordSecret, CsrfSecret: csrfSecret, } app.Store.Options = &sessions.Options{ Secure: true, HttpOnly: true, MaxAge: int((24 * time.Hour) / time.Second), } return app } type LoginModel struct { Username string UsernameError error PasswordError error CsrfToken string } // Csrf handler wrapper func (app *AuthCookie) Protect() func(http.Handler) http.Handler { return csrf.Protect([]byte(app.CsrfSecret), csrf.Secure(true), csrf.HttpOnly(true)) } // Verify Username func (app *AuthCookie) VerifyUsername(username string) error { if username == "" { return errors.New("Empty username") } return nil } // Verify password for user, check with file func (app *AuthCookie) VerifyPassword(username, password string, res http.ResponseWriter, req *http.Request) error { if password == "" { return errors.New("Empty password") } if username == "" { return nil } // open password file passfile, err := os.Open(filepath.Join(app.DataDir.Name(), username, ".password")) if err != nil { log.Println("Cannot open password file", err) return errors.New("Authentification failed") } defer passfile.Close() // read password file expected, err := ioutil.ReadAll(passfile) if err != nil { log.Println("Cannot read password file", err) return errors.New("Authentification failed") } // hash password and compare expectedStr := string(expected) var expectedMAC []byte fmt.Sscanf(expectedStr, "%x", &expectedMAC) mac := hmac.New(sha256.New, []byte(app.PasswordSecret)) mac.Write([]byte(password)) passwordMAC := mac.Sum(nil) if !hmac.Equal(passwordMAC, expectedMAC) { log.Printf("Unmatched password for %s: %x - %x\n", username, passwordMAC, expectedMAC) return errors.New("Authentification failed") } log.Printf("Authentification successful (" + username + ")") return nil } // Save username in session func (app *AuthCookie) SaveUsername(username string, res http.ResponseWriter, req *http.Request) { session := app.CurrentSession(res, req) session.Values[SESS_USER] = username session.Save(req, res) } // Test if a user is logged in func (app *AuthCookie) IsLoggedIn(res http.ResponseWriter, req *http.Request) bool { session := app.CurrentSession(res, req) return session != nil && session.Values[SESS_USER] != "" } // Current username (from session cookie) func (app *AuthCookie) Username(res http.ResponseWriter, req *http.Request) string { session := app.CurrentSession(res, req) if session == nil { return "" } val, _ := session.Values[SESS_USER].(string) return val } // Current session func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session { session, _ := app.Store.Get(req, SESSION) return session } // Redirect to home page func (app *AuthCookie) RedirectHome(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, "/", http.StatusSeeOther) } // ROUTES // // login: form func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) { model := LoginModel{ CsrfToken: csrf.Token(req), } if req.Method == http.MethodPost { model.Username = req.FormValue(USER) model.UsernameError = app.VerifyUsername(model.Username) model.PasswordError = app.VerifyPassword(model.Username, req.FormValue(PWD), res, req) if model.UsernameError == nil && model.PasswordError == nil { app.SaveUsername(model.Username, res, req) app.RedirectHome(res, req) return } } app.Templates.ExecuteTemplate(res, "login.html", model) } // logout: delete session and redirect to home func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) { app.SaveUsername("", res, req) app.RedirectHome(res, req) }