Example #1
0
func createSession(w http.ResponseWriter, r *http.Request, session *sessions.Session) *ServerSession {

	// Each session needs a unique ID in order to be saved.
	if session.ID == "" {
		session.ID = tokens.NewSessionID()
	}

	ss := &ServerSession{
		CSRFToken: tokens.NewCSRFToken(session.ID),
	}

	// Attempt to store the session. Remove the session if it's not stored
	// correctly.
	if err := ss.StoreSession(session.ID); err != nil {
		RemoveSession(session.ID)
		glog.Fatalln(err)
	}

	// Similarly, save it in our FS storage and set the user's cookie.
	if err := session.Save(r, w); err != nil {
		RemoveSession(session.ID)
		glog.Fatalln(err)
	}

	return ss
}
Example #2
0
// AuthenticateUser attempts to authenticate a user from the login page.
//
// It returns:
//     - An an enum indicating the user's authentication status.
//     - A pointer to dt.LoginData which holds information to be displayed.
//     - An HTTPError if applicable.
//
// Errors displayed to the user _must_ use proper punctuation.
func AuthenticateUser(w http.ResponseWriter, r *http.Request) (AuthStatus, *dt.LoginData, *HTTPError) {
	// Gather information about the user.
	session, validAuth, httperr := CheckSession(r)
	if httperr != nil {
		glog.Errorln(httperr.Err)
		return InvalidAuth, &dt.LoginData{}, &HTTPError{
			http.StatusInternalServerError, httperr.Err,
		}
	}

	ss, err := GetSession(session.ID)
	if err != nil {
		glog.Errorln(err)
	}

	// Technically this should never be true because if the cookie is
	// valid the user won't have to re-authenticate.
	if validAuth {
		glog.V(2).Infof("redirected to %s\n", paths.DashboardPath)

		return ValidAuth, &dt.LoginData{
			CSRF:  ss.CSRFToken,
			Redir: paths.DashboardPath,
		}, nil
	}

	// Now we can begin to gather form information.
	if err := r.ParseForm(); err != nil {
		glog.Errorln(err)
		return InvalidAuth, &dt.LoginData{
			CSRF: ss.CSRFToken,
		}, &HTTPError{http.StatusInternalServerError, err}
	}

	if !ValidCSRF(r, session, false) {
		return InvalidAuth, &dt.LoginData{
			CSRF:  ss.CSRFToken,
			Error: "Invalid CSRF token.",
		}, &HTTPError{http.StatusOK, ErrInvalidCSRFToken}
	}

	// Max email address/username is 255 characters, so if it's too long bail.
	// There's no reason to _not_ have a hard limit for email addresses.
	formID := r.PostFormValue("username")
	length, ok := validLoginInput(formID)

	// Username with a length of 0.
	if !length {
		return InvalidAuth, &dt.LoginData{
			CSRF:  ss.CSRFToken,
			Error: "Enter your username.",
		}, &HTTPError{http.StatusOK, ErrInvalidLogin}
	}

	if !ok {
		return InvalidAuth, &dt.LoginData{
			CSRF:  ss.CSRFToken,
			Error: "Enter a valid username.",
		}, &HTTPError{http.StatusOK, ErrInvalidLogin}
	}

	// No need to validate size. POSTs larger than client_max_body_size will
	// be discarded, and there's absolutely no reason why we should _ever_
	// have a limit on password length.
	formPassword := r.PostFormValue("password")

	// Now that the form data isn't blatantly invalid, create a new user
	// object to test the form data given to us.
	user, exists, err := database.CheckUser(formID)
	if err != nil {
		glog.Errorln(err)
	}

	// User does not exist. Package the error and display an error message.
	if !exists {

		session.AddFlash(ErrBadUsername.Error(), "_errors")
		if err := session.Save(r, w); err != nil {
			glog.Errorln(err)
			return InvalidAuth, &dt.LoginData{CSRF: ss.CSRFToken}, &HTTPError{
				http.StatusInternalServerError, err,
			}
		}

		// NOTE: It's _not_ a security risk to let the user know the given
		// username does not exist. If there's a public sign up page, then
		// they already have the ability to check if a username exists,
		// thus any "coy" language like, "The username/password are invalid"
		// just creates a bad user experience.
		// See: http://blog.codinghorror.com/the-god-login/
		return InvalidAuth, &dt.LoginData{
			CSRF:  ss.CSRFToken,
			Error: "Username does not exist.",
		}, &HTTPError{http.StatusOK, ErrBadUsername}
	}

	// Securely compare hashes.
	err = ComparePassword(user, []byte(formPassword), true)

	if err != nil {
		glog.Errorln(err)

		session.AddFlash(ErrBadPassword.Error(), "_errors")
		if err := session.Save(r, w); err != nil {
			glog.Errorln(err)
			return InvalidAuth, &dt.LoginData{
					CSRF: ss.CSRFToken,
				}, &HTTPError{
					http.StatusInternalServerError, ErrBadPassword,
				}
		}

		// bcrypt.CompareHashAndPassword will return one of two named errors:
		//     - ErrMismatchedHashAndPasword if the password doesn't match
		//       the hash.
		//     - ErrHashTooShort if the hash is too short to be a bcrypt hash
		if err == bcrypt.ErrMismatchedHashAndPassword {
			return InvalidAuth, &dt.LoginData{
				CSRF:  ss.CSRFToken,
				Error: "The password you entered is incorrect.",
			}, &HTTPError{http.StatusOK, ErrBadPassword}
		} else {
			glog.Errorln(err)
			return InvalidAuth, &dt.LoginData{
					CSRF: ss.CSRFToken}, &HTTPError{
					http.StatusInternalServerError, err,
				}
		}
	}

	// Default end date is now (ns) + our allowance, which is usually like
	// 8 hours or so.
	endDate := time.Now().UnixNano() + allowance

	// If the user selected the "remember me" checkbox, set the expiration
	// date to as high as it will go.
	remember := r.PostFormValue("remember")
	if remember == "true" {
		endDate = int64(^uint64(0) >> 1)
	}

	// Check if the CSRF/ID are set. It might not be set if the user didn't
	// have any cookies when they visited the login page.
	if session.ID == "" {
		session.ID = tokens.NewSessionID()
	}

	csrf, _ := getCSRF(session)

	ss.AuthToken = tokens.NewAuthToken()
	ss.CSRFToken = csrf
	ss.Email = user.Email
	ss.Date = endDate
	ss.School = user.School

	// Store the session and if it doesn't work try to remove it just
	// to be safe.
	//
	// (Note: this does *not* save the session in the user's
	// browser. While a more generic 'SaveSession' would be nice, it'd
	// also be mixing our auth and SQL 'modules' which is a bit sloppy.
	// I'd rather have to explicitly perform each step rather than
	// wade through a function that abstracts away arguably the most
	// important logic in the app.)
	err = ss.StoreSession(session.ID)
	if err != nil {
		RemoveSession(session.ID)

		glog.Errorln(err)
		return InvalidAuth, &dt.LoginData{
			CSRF: ss.CSRFToken,
		}, &HTTPError{http.StatusInternalServerError, err}
	}

	// Store some relevant values.
	session.Values[authToken] = ss.AuthToken
	session.Values[authDate] = ss.Date
	session.Values[csrfToken] = ss.CSRFToken
	session.Values["user"] = ss.Email
	session.Values["school"] = ss.School

	// Set cookie termination date for responsible clients.
	// Adjust ns to seconds because MaxAge assumes seconds.
	session.Options.MaxAge = int(endDate / nsConv)

	// Now we save the session in the user's browser.
	err = session.Save(r, w)
	if err != nil {
		glog.Errorln(err)
		return InvalidAuth, &dt.LoginData{
			CSRF: ss.CSRFToken,
		}, &HTTPError{http.StatusInternalServerError, err}
	}

	// Send the user to the dashboard.
	return ValidAuth, &dt.LoginData{
		CSRF:  ss.CSRFToken,
		Redir: paths.DashboardPath,
	}, nil
}