package admin import ( "crypto/hmac" "crypto/sha256" "errors" "fmt" "html/template" "io/ioutil" "log" "net/http" "os" "path/filepath" "time" "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 } // Constructor AuthCookie func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret string, data *os.File) *AuthCookie { app := &AuthCookie{ Templates: tpl, Store: sessions.NewCookieStore([]byte(sessionSecret)), DataDir: data, PasswordSecret: passwordSecret, } app.Store.Options = &sessions.Options{ Secure: true, HttpOnly: true, MaxAge: int((24 * time.Hour) / time.Second), } return app } // 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) { formErr := make(map[string]error) if req.Method == http.MethodPost { username := req.FormValue(USER) formErr[USER] = app.VerifyUsername(username) formErr[PWD] = app.VerifyPassword(username, req.FormValue(PWD), res, req) if formErr[USER] == nil && formErr[PWD] == nil { app.SaveUsername(username, res, req) app.RedirectHome(res, req) return } } app.Templates.ExecuteTemplate(res, "login.html", formErr) } // 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) }