diff --git a/Makefile b/Makefile index 490a71a..01efa03 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=0.1.7 +VERSION=0.2.0 all: deps fmt combined @@ -25,7 +25,7 @@ deps: go get labix.org/v2/mgo # added to fix travis issues go get code.google.com/p/go-uuid/uuid - go get code.google.com/p/go.crypto/bcrypt + go get golang.org/x/crypto/bcrypt test-deps: go get github.com/smartystreets/goconvey diff --git a/README.md b/README.md index 95acf55..2e7ddbd 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ MailHog is an email testing tool for developers: * See [Introduction to Jim](/docs/JIM.md) for more information * HTTP API to list, retrieve and delete messages * See [APIv1](/docs/APIv1.md) and [APIv2](/docs/APIv2.md) documentation for more information +* [HTTP basic authentication](docs/Auth.md) for MailHog UI and API * Multipart MIME support * Download individual MIME parts * In-memory message storage diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..c20ce15 --- /dev/null +++ b/config/config.go @@ -0,0 +1,27 @@ +package config + +import ( + "flag" + + "github.com/ian-kent/envconf" +) + +func DefaultConfig() *Config { + return &Config{ + AuthFile: "", + } +} + +type Config struct { + AuthFile string +} + +var cfg = DefaultConfig() + +func Configure() *Config { + return cfg +} + +func RegisterFlags() { + flag.StringVar(&cfg.AuthFile, "auth-file", envconf.FromEnvP("MH_AUTH_FILE", "").(string), "A username:bcryptpw mapping file") +} diff --git a/docs/Auth.md b/docs/Auth.md new file mode 100644 index 0000000..d3cf5bb --- /dev/null +++ b/docs/Auth.md @@ -0,0 +1,43 @@ +Authentication +============== + +HTTP basic authentication is supported using a password file. + +See [example-auth](example-auth) for an example. + +Authentication applies to all HTTP requests, including static content +and API endpoints. + +### Password file format + +The password file format is: + +* One user per line +* `username:password` +* Password is bcrypted + +By default, a bcrypt difficulty of 4 is used to reduce page load times. + +### Generating a bcrypted password + +You can use a MailHog shortcut to generate a bcrypted password: + + MailHog bcrypt + +### Enabling HTTP authentication + +To enable authentication, pass an `-auth-file` flag to MailHog: + + MailHog -auth-file=docs/example-auth + +This also works if you're running MailHog-UI and MailHog-Server separately: + + MailHog-Server -auth-file=docs/example-auth + MailHog-UI -auth-file=docs/example-auth + +## Future compatibility + +Authentication has been a bit of an experiment. + +The exact implementation may change over time, e.g. using sessions in the UI +and tokens for the API to avoid frequently bcrypting passwords. diff --git a/docs/example-auth b/docs/example-auth new file mode 100644 index 0000000..4e0df0f --- /dev/null +++ b/docs/example-auth @@ -0,0 +1 @@ +test:$2a$04$qxRo.ftFoNep7ld/5jfKtuBTnGqff/fZVyj53mUC5sVf9dtDLAi/S diff --git a/main.go b/main.go index 6f74d52..8322550 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "os" gohttp "net/http" @@ -14,20 +15,25 @@ import ( "github.com/mailhog/MailHog-UI/assets" cfgui "github.com/mailhog/MailHog-UI/config" "github.com/mailhog/MailHog-UI/web" + cfgcom "github.com/mailhog/MailHog/config" "github.com/mailhog/http" "github.com/mailhog/mhsendmail/cmd" + "golang.org/x/crypto/bcrypt" ) var apiconf *cfgapi.Config var uiconf *cfgui.Config +var comconf *cfgcom.Config var exitCh chan int func configure() { + cfgcom.RegisterFlags() cfgapi.RegisterFlags() cfgui.RegisterFlags() flag.Parse() apiconf = cfgapi.Configure() uiconf = cfgui.Configure() + comconf = cfgcom.Configure() } func main() { @@ -41,8 +47,28 @@ func main() { return } + if len(os.Args) > 1 && os.Args[1] == "bcrypt" { + var pw string + if len(os.Args) > 2 { + pw = os.Args[2] + } else { + // TODO: read from stdin + } + b, err := bcrypt.GenerateFromPassword([]byte(pw), 4) + if err != nil { + log.Fatalf("error bcrypting password: %s", err) + os.Exit(1) + } + fmt.Println(string(b)) + os.Exit(0) + } + configure() + if comconf.AuthFile != "" { + http.AuthFile(comconf.AuthFile) + } + exitCh = make(chan int) if uiconf.UIBindAddr == apiconf.APIBindAddr { cb := func(r gohttp.Handler) {