From 5a641ceca19cb43a37b990c8ea16e8e071dd91ba Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 21 Feb 2022 10:55:02 +0100 Subject: [PATCH] Implement IMAP auth provider --- auth/imap.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ auth/interface.go | 11 ++++++++ auth/middleware.go | 0 auth/postgresql.go | 0 cmd/tokidoki/main.go | 6 ++++ go.mod | 7 ++++- go.sum | 11 ++++++++ 7 files changed, 100 insertions(+), 1 deletion(-) delete mode 100644 auth/middleware.go delete mode 100644 auth/postgresql.go diff --git a/auth/imap.go b/auth/imap.go index e69de29..71fb1ae 100644 --- a/auth/imap.go +++ b/auth/imap.go @@ -0,0 +1,66 @@ +package auth + +import ( + "log" + "net/http" + + "github.com/emersion/go-imap/client" + "github.com/emersion/go-sasl" +) + +type IMAPProvider struct { + addr string + tls bool +} + +// Initializes a new IMAP auth provider with the given connection string. +func NewIMAP(addr string, tls bool) AuthProvider { + prov := &IMAPProvider{addr, tls} + conn, err := prov.dial() + if err != nil { + log.Fatalf("Error dialing configured IMAP auth server: %s", err.Error()) + } + conn.Close() + return prov +} + +func (prov *IMAPProvider) Middleware() func(http.Handler) http.Handler { + return func (next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prov.doAuth(next, w, r) + }) + } +} + +func (prov *IMAPProvider) doAuth(next http.Handler, + w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok { + w.Header().Add("WWW-Authenticate", `Basic realm="Please provide your IMAP credentials", charset="UTF-8"`) + http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized) + return + } + + conn, err := prov.dial() + if err != nil { + http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable) + return + } + defer conn.Close() + + auth := sasl.NewPlainClient("", user, pass) + if err := conn.Authenticate(auth); err != nil { + http.Error(w, "Invalid username or password", http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, r) +} + +func (prov *IMAPProvider) dial() (*client.Client, error) { + if prov.tls { + return client.DialTLS(prov.addr, nil) + } else { + return client.Dial(prov.addr) + } +} diff --git a/auth/interface.go b/auth/interface.go index e69de29..39b3d03 100644 --- a/auth/interface.go +++ b/auth/interface.go @@ -0,0 +1,11 @@ +package auth + +import ( + "net/http" +) + +// Abstracts the authentication backend for the server. +type AuthProvider interface { + // Returns HTTP middleware for performing authentication. + Middleware() func(http.Handler) http.Handler +} diff --git a/auth/middleware.go b/auth/middleware.go deleted file mode 100644 index e69de29..0000000 diff --git a/auth/postgresql.go b/auth/postgresql.go deleted file mode 100644 index e69de29..0000000 diff --git a/cmd/tokidoki/main.go b/cmd/tokidoki/main.go index 3279797..0d4de38 100644 --- a/cmd/tokidoki/main.go +++ b/cmd/tokidoki/main.go @@ -8,6 +8,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + + "git.sr.ht/~sircmpwn/tokidoki/auth" ) func main() { @@ -25,6 +27,10 @@ func main() { mux := chi.NewRouter() mux.Use(middleware.Logger) + // TODO: Configurable + authProvider := auth.NewIMAP("imap.migadu.com:993", true) + mux.Use(authProvider.Middleware()) + mux.Get("/", func (w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello world!\n")) }) diff --git a/go.mod b/go.mod index 788df4c..a516366 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,9 @@ module git.sr.ht/~sircmpwn/tokidoki go 1.17 -require github.com/go-chi/chi/v5 v5.0.7 // indirect +require ( + github.com/emersion/go-imap v1.2.0 // indirect + github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect + github.com/go-chi/chi/v5 v5.0.7 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum index 433d671..5086cd8 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,13 @@ +github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA= +github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= +github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs= +github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=