github.com/volatiletech/authboss@v2.4.1+incompatible/auth/auth.go (about)

     1  // Package auth implements password based user logins.
     2  package auth
     3  
     4  import (
     5  	"context"
     6  	"net/http"
     7  
     8  	"golang.org/x/crypto/bcrypt"
     9  
    10  	"github.com/volatiletech/authboss"
    11  )
    12  
    13  const (
    14  	// PageLogin is for identifying the login page for parsing & validation
    15  	PageLogin = "login"
    16  )
    17  
    18  func init() {
    19  	authboss.RegisterModule("auth", &Auth{})
    20  }
    21  
    22  // Auth module
    23  type Auth struct {
    24  	*authboss.Authboss
    25  }
    26  
    27  // Init module
    28  func (a *Auth) Init(ab *authboss.Authboss) (err error) {
    29  	a.Authboss = ab
    30  
    31  	if err = a.Authboss.Config.Core.ViewRenderer.Load(PageLogin); err != nil {
    32  		return err
    33  	}
    34  
    35  	a.Authboss.Config.Core.Router.Get("/login", a.Authboss.Core.ErrorHandler.Wrap(a.LoginGet))
    36  	a.Authboss.Config.Core.Router.Post("/login", a.Authboss.Core.ErrorHandler.Wrap(a.LoginPost))
    37  
    38  	return nil
    39  }
    40  
    41  // LoginGet simply displays the login form
    42  func (a *Auth) LoginGet(w http.ResponseWriter, r *http.Request) error {
    43  	data := authboss.HTMLData{}
    44  	if redir := r.URL.Query().Get(authboss.FormValueRedirect); len(redir) != 0 {
    45  		data[authboss.FormValueRedirect] = redir
    46  	}
    47  	return a.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
    48  }
    49  
    50  // LoginPost attempts to validate the credentials passed in
    51  // to log in a user.
    52  func (a *Auth) LoginPost(w http.ResponseWriter, r *http.Request) error {
    53  	logger := a.RequestLogger(r)
    54  
    55  	validatable, err := a.Authboss.Core.BodyReader.Read(PageLogin, r)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	// Skip validation since all the validation happens during the database lookup and
    61  	// password check.
    62  	creds := authboss.MustHaveUserValues(validatable)
    63  
    64  	pid := creds.GetPID()
    65  	pidUser, err := a.Authboss.Storage.Server.Load(r.Context(), pid)
    66  	if err == authboss.ErrUserNotFound {
    67  		logger.Infof("failed to load user requested by pid: %s", pid)
    68  		data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"}
    69  		return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
    70  	} else if err != nil {
    71  		return err
    72  	}
    73  
    74  	authUser := authboss.MustBeAuthable(pidUser)
    75  	password := authUser.GetPassword()
    76  
    77  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, pidUser))
    78  
    79  	var handled bool
    80  	err = bcrypt.CompareHashAndPassword([]byte(password), []byte(creds.GetPassword()))
    81  	if err != nil {
    82  		handled, err = a.Authboss.Events.FireAfter(authboss.EventAuthFail, w, r)
    83  		if err != nil {
    84  			return err
    85  		} else if handled {
    86  			return nil
    87  		}
    88  
    89  		logger.Infof("user %s failed to log in", pid)
    90  		data := authboss.HTMLData{authboss.DataErr: "Invalid Credentials"}
    91  		return a.Authboss.Core.Responder.Respond(w, r, http.StatusOK, PageLogin, data)
    92  	}
    93  
    94  	r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyValues, validatable))
    95  
    96  	handled, err = a.Events.FireBefore(authboss.EventAuth, w, r)
    97  	if err != nil {
    98  		return err
    99  	} else if handled {
   100  		return nil
   101  	}
   102  
   103  	handled, err = a.Events.FireBefore(authboss.EventAuthHijack, w, r)
   104  	if err != nil {
   105  		return err
   106  	} else if handled {
   107  		return nil
   108  	}
   109  
   110  	logger.Infof("user %s logged in", pid)
   111  	authboss.PutSession(w, authboss.SessionKey, pid)
   112  	authboss.DelSession(w, authboss.SessionHalfAuthKey)
   113  
   114  	handled, err = a.Authboss.Events.FireAfter(authboss.EventAuth, w, r)
   115  	if err != nil {
   116  		return err
   117  	} else if handled {
   118  		return nil
   119  	}
   120  
   121  	ro := authboss.RedirectOptions{
   122  		Code:             http.StatusTemporaryRedirect,
   123  		RedirectPath:     a.Authboss.Paths.AuthLoginOK,
   124  		FollowRedirParam: true,
   125  	}
   126  	return a.Authboss.Core.Redirector.Redirect(w, r, ro)
   127  }