Skip to content

Commit

Permalink
Merging sessionStore into authStore
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob Archibald committed Jan 20, 2017
1 parent 51e9227 commit 5f18c2d
Show file tree
Hide file tree
Showing 4 changed files with 515 additions and 567 deletions.
214 changes: 200 additions & 14 deletions authStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ import (
"time"
)

var emailCookieName = "Email"
var sessionCookieName = "Session"
var rememberMeCookieName = "RememberMe"
var emailRegex = regexp.MustCompile(`^(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$`)

const emailExpireMins int = 60 * 24 * 365 // 1 year
const emailExpireDuration time.Duration = time.Duration(emailExpireMins) * time.Minute
const sessionRenewDuration time.Duration = 5 * time.Minute
const sessionExpireDuration time.Duration = time.Hour
const rememberMeRenewDuration time.Duration = time.Hour
const rememberMeExpireDuration time.Duration = time.Hour * 24 * 30 // 30 days
const passwordValidationMessage string = "Password must be between 7 and 20 characters"

type authStorer interface {
GetSession() (*loginSession, error)
GetBasicAuth() (*loginSession, error)
Expand All @@ -30,23 +43,55 @@ type emailCookie struct {
ExpireTimeUTC time.Time
}

type authStore struct {
backend backender
sessionStore sessionStorer
mailer mailer
cookieStore cookieStorer
r *http.Request
type sessionCookie struct {
SessionID string
RenewTimeUTC time.Time
ExpireTimeUTC time.Time
}

var emailRegex = regexp.MustCompile(`^(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$`)
type rememberMeCookie struct {
Selector string
Token string
RenewTimeUTC time.Time
ExpireTimeUTC time.Time
}

type authStore struct {
backend backender
mailer mailer
cookieStore cookieStorer
r *http.Request
}

func newAuthStore(b backender, mailer mailer, w http.ResponseWriter, r *http.Request, customPrefix string, cookieKey []byte, secureOnlyCookie bool) authStorer {
sessionStore := newSessionStore(b, w, r, customPrefix, cookieKey, secureOnlyCookie)
return &authStore{b, sessionStore, mailer, newCookieStore(w, r, cookieKey, secureOnlyCookie), r}
emailCookieName = customPrefix + "Email"
sessionCookieName = customPrefix + "Session"
rememberMeCookieName = customPrefix + "RememberMe"
return &authStore{b, mailer, newCookieStore(w, r, cookieKey, secureOnlyCookie), r}
}

func (s *authStore) GetSession() (*loginSession, error) {
return s.sessionStore.GetSession()
cookie, err := s.getSessionCookie()
if err != nil || cookie.SessionID == "" { // impossible to get the session if there is no cookie
return nil, newAuthError("Session cookie not found", err)
}
sessionHash, err := decodeStringToHash(cookie.SessionID)
if err != nil {
return nil, newAuthError("Unable to decode session cookie", err)
}

if cookie.RenewTimeUTC.Before(time.Now().UTC()) || cookie.ExpireTimeUTC.Before(time.Now().UTC()) {
return s.renewSession(cookie.SessionID, sessionHash, &cookie.RenewTimeUTC, &cookie.ExpireTimeUTC)
}

session, err := s.backend.GetSession(sessionHash)
if err != nil {
if err == errSessionNotFound {
s.deleteSessionCookie()
}
return nil, newLoggedError("Failed to verify session", err)
}
return session, nil
}

func (s *authStore) GetBasicAuth() (*loginSession, error) {
Expand All @@ -65,6 +110,71 @@ func (s *authStore) GetBasicAuth() (*loginSession, error) {
return nil, newAuthError("Problem decoding credentials from basic auth", nil)
}

func (s *authStore) getRememberMe() (*rememberMeSession, error) {
cookie, err := s.getRememberMeCookie()
if err != nil || cookie.Selector == "" { // impossible to get the remember Me if there is no cookie
return nil, newAuthError("RememberMe cookie not found", err)
}
if cookie.ExpireTimeUTC.Before(time.Now().UTC()) {
s.deleteRememberMeCookie()
return nil, newAuthError("RememberMe cookie has expired", nil)
}

rememberMe, err := s.backend.GetRememberMe(cookie.Selector)
if err != nil {
if err == errRememberMeNotFound {
s.deleteRememberMeCookie()
}
return nil, newLoggedError("Unable to find matching RememberMe in DB", err)
}
if !encodedHashEquals(cookie.Token, rememberMe.TokenHash) {
s.deleteRememberMeCookie()
return nil, newLoggedError("RememberMe cookie doesn't match backend token", nil)
}
if rememberMe.RenewTimeUTC.Before(time.Now().UTC()) {
rememberMe, err = s.backend.RenewRememberMe(cookie.Selector, time.Now().UTC().Add(rememberMeRenewDuration))
if err != nil {
if err == errRememberMeNotFound {
s.deleteRememberMeCookie()
}
return nil, newLoggedError("Unable to renew RememberMe", err)
}
}
return rememberMe, nil
}

func (s *authStore) renewSession(sessionID, sessionHash string, renewTimeUTC, expireTimeUTC *time.Time) (*loginSession, error) {
if renewTimeUTC.Before(time.Now().UTC()) && expireTimeUTC.After(time.Now().UTC()) {
session, err := s.backend.RenewSession(sessionHash, time.Now().UTC().Add(sessionRenewDuration))
if err != nil {
return nil, newLoggedError("Unable to renew session", err)
}

if err = s.saveSessionCookie(sessionID, session.RenewTimeUTC, session.ExpireTimeUTC); err != nil {
return nil, err
}
return session, nil
}

_, err := s.getRememberMe()
if err != nil {
return nil, newAuthError("Unable to renew session", err)
}

session, err := s.backend.RenewSession(sessionHash, time.Now().UTC().Add(sessionRenewDuration))
if err != nil {
if err == errSessionNotFound {
s.deleteSessionCookie()
}
return nil, newLoggedError("Problem renewing session", err)
}

if err = s.saveSessionCookie(sessionID, session.RenewTimeUTC, session.ExpireTimeUTC); err != nil {
return nil, err
}
return session, nil
}

/******************************** Login ***********************************************/
func (s *authStore) Login() error {
credentials, err := getCredentials(s.r)
Expand Down Expand Up @@ -96,7 +206,53 @@ func (s *authStore) login(email, password string, rememberMe bool) (*loginSessio
if err != nil {
return nil, err
}
return s.sessionStore.CreateSession(email, rememberMe)
return s.createSession(email, rememberMe)
}

func (s *authStore) createSession(email string, rememberMe bool) (*loginSession, error) {
var err error
var selector, token, tokenHash string
if rememberMe {
selector, token, tokenHash, err = generateSelectorTokenAndHash()
if err != nil {
return nil, newLoggedError("Unable to generate RememberMe", err)
}
}
sessionID, sessionHash, err := generateStringAndHash()
if err != nil {
return nil, newLoggedError("Problem generating sessionId", nil)
}

session, remember, err := s.backend.CreateSession(email, sessionHash, time.Now().UTC().Add(sessionRenewDuration), time.Now().UTC().Add(sessionExpireDuration), rememberMe, selector, tokenHash, time.Now().UTC().Add(rememberMeRenewDuration), time.Now().UTC().Add(rememberMeExpireDuration))
if err != nil {
return nil, newLoggedError("Unable to create new session", err)
}

sessionCookie, err := s.getSessionCookie()
if err == nil {
oldSessionHash, err := decodeStringToHash(sessionCookie.SessionID)
if err == nil {
s.backend.InvalidateSession(oldSessionHash)
}
}

rememberCookie, err := s.getRememberMeCookie()
if err == nil {
s.backend.InvalidateRememberMe(rememberCookie.Selector)
s.deleteRememberMeCookie()
}

if rememberMe {
err := s.saveRememberMeCookie(selector, token, remember.RenewTimeUTC, remember.ExpireTimeUTC)
if err != nil {
return nil, newAuthError("Unable to save rememberMe cookie", err)
}
}
err = s.saveSessionCookie(sessionID, session.RenewTimeUTC, session.ExpireTimeUTC)
if err != nil {
return nil, err
}
return session, nil
}

func isValidPassword(password string) bool {
Expand Down Expand Up @@ -205,7 +361,7 @@ func (s *authStore) createProfile(fullName, organization, password, picturePath
return newLoggedError("Unable to create login", err)
}

_, err = s.sessionStore.CreateSession(session.Email, false)
_, err = s.createSession(session.Email, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -284,15 +440,47 @@ func (s *authStore) getEmailCookie() (*emailCookie, error) {
return email, s.cookieStore.Get(emailCookieName, email)
}

func (s *authStore) getSessionCookie() (*sessionCookie, error) {
session := &sessionCookie{}
return session, s.cookieStore.Get(sessionCookieName, session)
}

func (s *authStore) getRememberMeCookie() (*rememberMeCookie, error) {
rememberMe := &rememberMeCookie{}
return rememberMe, s.cookieStore.Get(rememberMeCookieName, rememberMe)
}

func (s *authStore) deleteEmailCookie() {
s.cookieStore.Delete(emailCookieName)
}

func (s *authStore) deleteSessionCookie() {
s.cookieStore.Delete(sessionCookieName)
}

func (s *authStore) deleteRememberMeCookie() {
s.cookieStore.Delete(rememberMeCookieName)
}

func (s *authStore) saveEmailCookie(emailVerificationCode string, expireTimeUTC time.Time) error {
cookie := emailCookie{EmailVerificationCode: emailVerificationCode, ExpireTimeUTC: expireTimeUTC}
return s.cookieStore.PutWithExpire(emailCookieName, emailExpireMins, &cookie)
}

func (s *authStore) saveSessionCookie(sessionID string, renewTimeUTC, expireTimeUTC time.Time) error {
cookie := sessionCookie{SessionID: sessionID, RenewTimeUTC: renewTimeUTC, ExpireTimeUTC: expireTimeUTC}
err := s.cookieStore.Put(sessionCookieName, &cookie)
if err != nil {
return newAuthError("Error saving session cookie", err)
}
return nil
}

func (s *authStore) saveRememberMeCookie(selector, token string, renewTimeUTC, expireTimeUTC time.Time) error {
cookie := rememberMeCookie{Selector: selector, Token: token, RenewTimeUTC: renewTimeUTC, ExpireTimeUTC: expireTimeUTC}
return s.cookieStore.Put(rememberMeCookieName, &cookie)
}

type registration struct {
Email string
}
Expand Down Expand Up @@ -392,8 +580,6 @@ func getJSON(r *http.Request, result interface{}) error {
return json.Unmarshal(body, result)
}

const passwordValidationMessage string = "Password must be between 7 and 20 characters"

func isValidEmail(email string) bool {
return len(email) <= 254 && len(email) >= 6 && emailRegex.MatchString(email) == true
}
Loading

0 comments on commit 5f18c2d

Please sign in to comment.