package auth import ( "net/http" "github.com/rs/zerolog/log" "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.Fatal().Err(err).Msg("error dialing configured IMAP auth server") } 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 { log.Warn().Err(err).Msg("auth dial error") 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 { log.Debug().Str("user", user).Err(err).Msg("auth error") http.Error(w, "Invalid username or password", http.StatusUnauthorized) return } conn.Close() authCtx := AuthContext{ AuthMethod: "imap", UserName: user, } ctx := NewContext(r.Context(), &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) } }