1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-01-26 05:27:33 +02:00
authboss/authboss.go

137 lines
3.9 KiB
Go
Raw Normal View History

/*
Package authboss is a modular authentication system for the web. It tries to
remove as much boilerplate and "hard things" as possible so that each time you
start a new web project in Go, you can plug it in, configure and be off to the
2015-03-16 21:38:00 -07:00
races without having to think about how to store passwords or remember tokens.
*/
2016-12-19 22:43:51 -08:00
package authboss
2018-03-09 13:11:08 -08:00
import (
"context"
2018-07-17 07:09:38 -07:00
"fmt"
"net/http"
2018-07-17 07:09:38 -07:00
"net/url"
"path"
2018-03-09 13:11:08 -08:00
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
)
// Authboss contains a configuration and other details for running.
type Authboss struct {
Config
Events *Events
loadedModules map[string]Moduler
}
// New makes a new instance of authboss with a default
// configuration.
func New() *Authboss {
ab := &Authboss{}
ab.loadedModules = make(map[string]Moduler)
ab.Events = NewEvents()
ab.Config.Defaults()
return ab
}
// Init authboss, modules, renderers
func (a *Authboss) Init(modulesToLoad ...string) error {
if len(modulesToLoad) == 0 {
modulesToLoad = RegisteredModules()
}
for _, name := range modulesToLoad {
if err := a.loadModule(name); err != nil {
return errors.Errorf("module %s failed to load: %+v", name, err)
}
}
return nil
}
2015-03-16 22:58:32 -07:00
2018-03-09 13:11:08 -08:00
// UpdatePassword updates the password field of a user using the same semantics
// that register/auth do to create and verify passwords. It saves this using the storer.
//
// In addition to that, it also invalidates any remember me tokens, if the storer supports
// that kind of operation.
//
// If it's also desirable to log the user out, use: authboss.DelKnown(Session|Cookie)
func (a *Authboss) UpdatePassword(ctx context.Context, user AuthableUser, newPassword string) error {
pass, err := bcrypt.GenerateFromPassword([]byte(newPassword), a.Config.Modules.BCryptCost)
if err != nil {
return err
}
2018-03-09 13:11:08 -08:00
user.PutPassword(string(pass))
storer := a.Config.Storage.Server
if err := storer.Save(ctx, user); err != nil {
return err
}
2015-03-16 22:58:32 -07:00
2018-03-09 13:11:08 -08:00
rmStorer, ok := storer.(RememberingServerStorer)
if !ok {
return nil
}
2015-03-16 22:58:32 -07:00
return rmStorer.DelRememberTokens(ctx, user.GetPID())
2015-03-16 22:58:32 -07:00
}
2018-07-17 07:09:38 -07:00
// Middleware prevents someone from accessing a route they are not allowed to.
// It allows the user through if they are logged in.
//
// If redirectToLogin is true, the user will be redirected to the login page, otherwise they will
// get a 404. The redirect goes to: mountPath/login, this means it's expected that the auth module
// is loaded if this is set to true.
//
// If allowHalfAuth is true then half-authed users are allowed through, otherwise a half-authed
// user will not be allowed through.
2018-08-22 21:34:38 -07:00
func Middleware(ab *Authboss, redirectToLogin bool, allowHalfAuth bool, force2fa bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := ab.RequestLogger(r)
2018-07-17 07:09:38 -07:00
fail := func(w http.ResponseWriter, r *http.Request) {
if redirectToLogin {
log.Infof("redirecting unauthorized user to login from: %s", r.URL.Path)
vals := make(url.Values)
vals.Set(FormValueRedirect, r.URL.Path)
ro := RedirectOptions{
Code: http.StatusTemporaryRedirect,
Failure: "please re-login",
RedirectPath: path.Join(ab.Config.Paths.Mount, fmt.Sprintf("/login?%s", vals.Encode())),
}
if err := ab.Config.Core.Redirector.Redirect(w, r, ro); err != nil {
log.Errorf("failed to redirect user during authboss.Middleware redirect: %+v", err)
return
}
}
log.Infof("not found for unauthorized user at: %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}
2018-08-22 21:34:38 -07:00
if !allowHalfAuth && !IsFullyAuthed(r) || !force2fa && !IsTwoFactored(r) {
2018-07-17 07:09:38 -07:00
fail(w, r)
return
}
if u, err := ab.LoadCurrentUser(&r); err != nil {
log.Errorf("error fetching current user: %+v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else if u == nil {
2018-07-17 07:09:38 -07:00
fail(w, r)
return
} else {
next.ServeHTTP(w, r)
}
})
}
}