package auth import ( "context" "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 } authCtx := AuthContext{ AuthMethod: "imap", UserName: user, } ctx := context.WithValue(r.Context(), AuthCtxKey, &authCtx) r = r.WithContext(ctx) 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) } }