mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-15 20:13:16 +02:00
Merge branch 'feature/send-auth' into develop
This commit is contained in:
15
cmd/root.go
15
cmd/root.go
@@ -108,6 +108,10 @@ func init() {
|
|||||||
rootCmd.Flags().BoolVar(&config.DisableHTTPCompression, "disable-http-compression", config.DisableHTTPCompression, "Disable HTTP compression support (web UI & API)")
|
rootCmd.Flags().BoolVar(&config.DisableHTTPCompression, "disable-http-compression", config.DisableHTTPCompression, "Disable HTTP compression support (web UI & API)")
|
||||||
rootCmd.Flags().BoolVar(&config.HideDeleteAllButton, "hide-delete-all-button", config.HideDeleteAllButton, "Hide the \"Delete all\" button in the web UI")
|
rootCmd.Flags().BoolVar(&config.HideDeleteAllButton, "hide-delete-all-button", config.HideDeleteAllButton, "Hide the \"Delete all\" button in the web UI")
|
||||||
|
|
||||||
|
// Send API
|
||||||
|
rootCmd.Flags().StringVar(&config.SendAPIAuthFile, "send-api-auth-file", config.SendAPIAuthFile, "A password file for Send API authentication")
|
||||||
|
rootCmd.Flags().BoolVar(&config.SendAPIAuthAcceptAny, "send-api-auth-accept-any", config.SendAPIAuthAcceptAny, "Accept any username and password for the Send API endpoint, including none")
|
||||||
|
|
||||||
// SMTP server
|
// SMTP server
|
||||||
rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port")
|
rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port")
|
||||||
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication")
|
rootCmd.Flags().StringVar(&config.SMTPAuthFile, "smtp-auth-file", config.SMTPAuthFile, "A password file for SMTP authentication")
|
||||||
@@ -249,6 +253,15 @@ func initConfigFromEnv() {
|
|||||||
config.HideDeleteAllButton = true
|
config.HideDeleteAllButton = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send API
|
||||||
|
config.SendAPIAuthFile = os.Getenv("MP_SEND_API_AUTH_FILE")
|
||||||
|
if err := auth.SetSendAPIAuth(os.Getenv("MP_SEND_API_AUTH")); err != nil {
|
||||||
|
logger.Log().Error(err.Error())
|
||||||
|
}
|
||||||
|
if getEnabledFromEnv("MP_SEND_API_AUTH_ACCEPT_ANY") {
|
||||||
|
config.SendAPIAuthAcceptAny = true
|
||||||
|
}
|
||||||
|
|
||||||
// SMTP server
|
// SMTP server
|
||||||
if len(os.Getenv("MP_SMTP_BIND_ADDR")) > 0 {
|
if len(os.Getenv("MP_SMTP_BIND_ADDR")) > 0 {
|
||||||
config.SMTPListen = os.Getenv("MP_SMTP_BIND_ADDR")
|
config.SMTPListen = os.Getenv("MP_SMTP_BIND_ADDR")
|
||||||
@@ -362,9 +375,9 @@ func initConfigFromEnv() {
|
|||||||
func initDeprecatedConfigFromEnv() {
|
func initDeprecatedConfigFromEnv() {
|
||||||
// deprecated 2024/04/12 - but will not be removed to maintain backwards compatibility
|
// deprecated 2024/04/12 - but will not be removed to maintain backwards compatibility
|
||||||
if len(os.Getenv("MP_DATA_FILE")) > 0 {
|
if len(os.Getenv("MP_DATA_FILE")) > 0 {
|
||||||
|
logger.Log().Warn("ENV MP_DATA_FILE has been deprecated, use MP_DATABASE")
|
||||||
config.Database = os.Getenv("MP_DATA_FILE")
|
config.Database = os.Getenv("MP_DATA_FILE")
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated 2023/03/12
|
// deprecated 2023/03/12
|
||||||
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
|
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
|
||||||
logger.Log().Warn("ENV MP_UI_SSL_CERT has been deprecated, use MP_UI_TLS_CERT")
|
logger.Log().Warn("ENV MP_UI_SSL_CERT has been deprecated, use MP_UI_TLS_CERT")
|
||||||
|
@@ -72,6 +72,12 @@ var (
|
|||||||
// DisableHTTPCompression will explicitly disable HTTP compression in the web UI and API
|
// DisableHTTPCompression will explicitly disable HTTP compression in the web UI and API
|
||||||
DisableHTTPCompression bool
|
DisableHTTPCompression bool
|
||||||
|
|
||||||
|
// SendAPIAuthFile for Send API authentication
|
||||||
|
SendAPIAuthFile string
|
||||||
|
|
||||||
|
// SendAPIAuthAcceptAny accepts any username/password for the send API endpoint, including none
|
||||||
|
SendAPIAuthAcceptAny bool
|
||||||
|
|
||||||
// SMTPTLSCert file
|
// SMTPTLSCert file
|
||||||
SMTPTLSCert string
|
SMTPTLSCert string
|
||||||
|
|
||||||
@@ -289,6 +295,7 @@ func VerifyConfig() error {
|
|||||||
return errors.New("[ui] HTTP bind should be in the format of <ip>:<port>")
|
return errors.New("[ui] HTTP bind should be in the format of <ip>:<port>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web UI & API
|
||||||
if UIAuthFile != "" {
|
if UIAuthFile != "" {
|
||||||
UIAuthFile = filepath.Clean(UIAuthFile)
|
UIAuthFile = filepath.Clean(UIAuthFile)
|
||||||
|
|
||||||
@@ -323,6 +330,35 @@ func VerifyConfig() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send API
|
||||||
|
if SendAPIAuthFile != "" {
|
||||||
|
SendAPIAuthFile = filepath.Clean(SendAPIAuthFile)
|
||||||
|
|
||||||
|
if !isFile(SendAPIAuthFile) {
|
||||||
|
return fmt.Errorf("[send-api] password file not found or readable: %s", SendAPIAuthFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(SendAPIAuthFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth.SetSendAPIAuth(string(b)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log().Info("[send-api] enabling basic authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth.SendAPICredentials != nil && SendAPIAuthAcceptAny {
|
||||||
|
return errors.New("[send-api] authentication cannot use both credentials and --send-api-auth-accept-any")
|
||||||
|
}
|
||||||
|
|
||||||
|
if SendAPIAuthAcceptAny && auth.UICredentials != nil {
|
||||||
|
logger.Log().Info("[send-api] disabling authentication")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMTP server
|
||||||
if SMTPTLSCert != "" && SMTPTLSKey == "" || SMTPTLSCert == "" && SMTPTLSKey != "" {
|
if SMTPTLSCert != "" && SMTPTLSKey == "" || SMTPTLSCert == "" && SMTPTLSKey != "" {
|
||||||
return errors.New("[smtp] you must provide both an SMTP TLS certificate and a key")
|
return errors.New("[smtp] you must provide both an SMTP TLS certificate and a key")
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
// UICredentials passwords
|
// UICredentials passwords
|
||||||
UICredentials *htpasswd.File
|
UICredentials *htpasswd.File
|
||||||
|
// SendAPICredentials passwords
|
||||||
|
SendAPICredentials *htpasswd.File
|
||||||
// SMTPCredentials passwords
|
// SMTPCredentials passwords
|
||||||
SMTPCredentials *htpasswd.File
|
SMTPCredentials *htpasswd.File
|
||||||
// POP3Credentials passwords
|
// POP3Credentials passwords
|
||||||
@@ -36,6 +38,25 @@ func SetUIAuth(s string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSendAPIAuth will set Send API credentials
|
||||||
|
func SetSendAPIAuth(s string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
credentials := credentialsFromString(s)
|
||||||
|
if len(credentials) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReader(strings.Join(credentials, "\n"))
|
||||||
|
|
||||||
|
SendAPICredentials, err = htpasswd.NewFromReader(r, htpasswd.DefaultSystems, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetSMTPAuth will set SMTP credentials
|
// SetSMTPAuth will set SMTP credentials
|
||||||
func SetSMTPAuth(s string) error {
|
func SetSMTPAuth(s string) error {
|
||||||
var err error
|
var err error
|
||||||
|
@@ -158,7 +158,7 @@ func apiRoutes() *mux.Router {
|
|||||||
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE")
|
r.HandleFunc(config.Webroot+"api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.DeleteSearch)).Methods("DELETE")
|
r.HandleFunc(config.Webroot+"api/v1/search", middleWareFunc(apiv1.DeleteSearch)).Methods("DELETE")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/send", middleWareFunc(apiv1.SendMessageHandler)).Methods("POST")
|
r.HandleFunc(config.Webroot+"api/v1/send", sendAPIAuthMiddleware(apiv1.SendMessageHandler)).Methods("POST")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.GetAllTags)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.GetAllTags)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.SetMessageTags)).Methods("PUT")
|
r.HandleFunc(config.Webroot+"api/v1/tags", middleWareFunc(apiv1.SetMessageTags)).Methods("PUT")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/tags/{tag}", middleWareFunc(apiv1.RenameTag)).Methods("PUT")
|
r.HandleFunc(config.Webroot+"api/v1/tags/{tag}", middleWareFunc(apiv1.RenameTag)).Methods("PUT")
|
||||||
@@ -198,6 +198,48 @@ func basicAuthResponse(w http.ResponseWriter) {
|
|||||||
_, _ = w.Write([]byte("Unauthorised.\n"))
|
_, _ = w.Write([]byte("Unauthorised.\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendAPIAuthMiddleware handles authentication specifically for the send API endpoint
|
||||||
|
// It can use dedicated send API authentication or accept any credentials based on configuration
|
||||||
|
func sendAPIAuthMiddleware(fn http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If send API auth accept any is enabled, bypass all authentication
|
||||||
|
if config.SendAPIAuthAcceptAny {
|
||||||
|
// Temporarily disable UI auth for this request
|
||||||
|
originalCredentials := auth.UICredentials
|
||||||
|
auth.UICredentials = nil
|
||||||
|
defer func() { auth.UICredentials = originalCredentials }()
|
||||||
|
// Call the standard middleware
|
||||||
|
middleWareFunc(fn)(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Send API credentials are configured, only accept those credentials
|
||||||
|
if auth.SendAPICredentials != nil {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
basicAuthResponse(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !auth.SendAPICredentials.Match(user, pass) {
|
||||||
|
basicAuthResponse(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid Send API credentials - bypass UI auth and call function directly
|
||||||
|
originalCredentials := auth.UICredentials
|
||||||
|
auth.UICredentials = nil
|
||||||
|
defer func() { auth.UICredentials = originalCredentials }()
|
||||||
|
middleWareFunc(fn)(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Send API credentials configured - fall back to UI auth
|
||||||
|
middleWareFunc(fn)(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
type gzipResponseWriter struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
@@ -239,7 +281,9 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
|
|||||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.UICredentials != nil {
|
// Check basic authentication headers if configured.
|
||||||
|
// OPTIONS requests are skipped if CORS is enabled, since browsers omit credentials for preflight.
|
||||||
|
if !(AccessControlAllowOrigin != "" && r.Method == http.MethodOptions) && auth.UICredentials != nil {
|
||||||
user, pass, ok := r.BasicAuth()
|
user, pass, ok := r.BasicAuth()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@@ -13,10 +13,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/axllent/mailpit/config"
|
"github.com/axllent/mailpit/config"
|
||||||
|
"github.com/axllent/mailpit/internal/auth"
|
||||||
"github.com/axllent/mailpit/internal/logger"
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
"github.com/axllent/mailpit/internal/storage"
|
"github.com/axllent/mailpit/internal/storage"
|
||||||
"github.com/axllent/mailpit/server/apiv1"
|
"github.com/axllent/mailpit/server/apiv1"
|
||||||
"github.com/jhillyerd/enmime/v2"
|
"github.com/jhillyerd/enmime/v2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -24,6 +26,18 @@ var (
|
|||||||
Read bool
|
Read bool
|
||||||
IDs []string
|
IDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared test message structure for consistency
|
||||||
|
testSendMessage = map[string]interface{}{
|
||||||
|
"From": map[string]string{
|
||||||
|
"Email": "test@example.com",
|
||||||
|
},
|
||||||
|
"To": []map[string]string{
|
||||||
|
{"Email": "recipient@example.com"},
|
||||||
|
},
|
||||||
|
"Subject": "Test",
|
||||||
|
"Text": "Test message",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIv1Messages(t *testing.T) {
|
func TestAPIv1Messages(t *testing.T) {
|
||||||
@@ -312,6 +326,157 @@ func TestAPIv1Send(t *testing.T) {
|
|||||||
assertEqual(t, `This is a plain text attachment`, string(attachmentBytes), "wrong Attachment content")
|
assertEqual(t, `This is a plain text attachment`, string(attachmentBytes), "wrong Attachment content")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSendAPIAuthMiddleware(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer storage.Close()
|
||||||
|
|
||||||
|
// Test 1: Send API with accept-any enabled (should bypass all auth)
|
||||||
|
t.Run("SendAPIAuthAcceptAny", func(t *testing.T) {
|
||||||
|
// Set up UI auth and enable accept-any for send API
|
||||||
|
originalSendAPIAuthAcceptAny := config.SendAPIAuthAcceptAny
|
||||||
|
originalUICredentials := auth.UICredentials
|
||||||
|
defer func() {
|
||||||
|
config.SendAPIAuthAcceptAny = originalSendAPIAuthAcceptAny
|
||||||
|
auth.UICredentials = originalUICredentials
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Enable accept-any for send API
|
||||||
|
config.SendAPIAuthAcceptAny = true
|
||||||
|
|
||||||
|
// Set up UI auth that would normally block requests
|
||||||
|
testHash, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetUIAuth("testuser:" + string(testHash))
|
||||||
|
|
||||||
|
r := apiRoutes()
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// Should succeed without any auth headers
|
||||||
|
jsonData, _ := json.Marshal(testSendMessage)
|
||||||
|
_, err := clientPost(ts.URL+"/api/v1/send", string(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected send to succeed with accept-any, got error: %s", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 2: Send API with dedicated credentials
|
||||||
|
t.Run("SendAPIWithDedicatedCredentials", func(t *testing.T) {
|
||||||
|
originalSendAPIAuthAcceptAny := config.SendAPIAuthAcceptAny
|
||||||
|
originalUICredentials := auth.UICredentials
|
||||||
|
originalSendAPICredentials := auth.SendAPICredentials
|
||||||
|
defer func() {
|
||||||
|
config.SendAPIAuthAcceptAny = originalSendAPIAuthAcceptAny
|
||||||
|
auth.UICredentials = originalUICredentials
|
||||||
|
auth.SendAPICredentials = originalSendAPICredentials
|
||||||
|
}()
|
||||||
|
|
||||||
|
config.SendAPIAuthAcceptAny = false
|
||||||
|
|
||||||
|
// Set up UI auth
|
||||||
|
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||||
|
|
||||||
|
// Set up dedicated Send API auth
|
||||||
|
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetSendAPIAuth("senduser:" + string(sendHash))
|
||||||
|
|
||||||
|
r := apiRoutes()
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(testSendMessage)
|
||||||
|
|
||||||
|
// Should succeed with correct Send API credentials
|
||||||
|
_, err := clientPostWithAuth(ts.URL+"/api/v1/send", string(jsonData), "senduser", "sendpass")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected send to succeed with correct Send API credentials, got error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should fail with wrong Send API credentials
|
||||||
|
_, err = clientPostWithAuth(ts.URL+"/api/v1/send", string(jsonData), "senduser", "wrongpass")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected send to fail with wrong Send API credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should fail with UI credentials when Send API credentials are set
|
||||||
|
_, err = clientPostWithAuth(ts.URL+"/api/v1/send", string(jsonData), "uiuser", "uipass")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected send to fail with UI credentials when Send API credentials are required")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 3: Send API fallback to UI auth when no Send API auth is configured
|
||||||
|
t.Run("SendAPIFallbackToUIAuth", func(t *testing.T) {
|
||||||
|
originalSendAPIAuthAcceptAny := config.SendAPIAuthAcceptAny
|
||||||
|
originalUICredentials := auth.UICredentials
|
||||||
|
originalSendAPICredentials := auth.SendAPICredentials
|
||||||
|
defer func() {
|
||||||
|
config.SendAPIAuthAcceptAny = originalSendAPIAuthAcceptAny
|
||||||
|
auth.UICredentials = originalUICredentials
|
||||||
|
auth.SendAPICredentials = originalSendAPICredentials
|
||||||
|
}()
|
||||||
|
|
||||||
|
config.SendAPIAuthAcceptAny = false
|
||||||
|
auth.SendAPICredentials = nil
|
||||||
|
|
||||||
|
// Set up only UI auth
|
||||||
|
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||||
|
|
||||||
|
r := apiRoutes()
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(testSendMessage)
|
||||||
|
|
||||||
|
// Should succeed with UI credentials when no Send API auth is configured
|
||||||
|
_, err := clientPostWithAuth(ts.URL+"/api/v1/send", string(jsonData), "uiuser", "uipass")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected send to succeed with UI credentials when no Send API auth configured, got error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should fail without any credentials
|
||||||
|
_, err = clientPost(ts.URL+"/api/v1/send", string(jsonData))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected send to fail without credentials when UI auth is required")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 4: Regular API endpoints should not be affected by Send API auth settings
|
||||||
|
t.Run("RegularAPINotAffectedBySendAPIAuth", func(t *testing.T) {
|
||||||
|
originalSendAPIAuthAcceptAny := config.SendAPIAuthAcceptAny
|
||||||
|
originalUICredentials := auth.UICredentials
|
||||||
|
originalSendAPICredentials := auth.SendAPICredentials
|
||||||
|
defer func() {
|
||||||
|
config.SendAPIAuthAcceptAny = originalSendAPIAuthAcceptAny
|
||||||
|
auth.UICredentials = originalUICredentials
|
||||||
|
auth.SendAPICredentials = originalSendAPICredentials
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set up UI auth and Send API auth
|
||||||
|
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||||
|
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
|
||||||
|
auth.SetSendAPIAuth("senduser:" + string(sendHash))
|
||||||
|
|
||||||
|
r := apiRoutes()
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// Regular API endpoint should require UI credentials, not Send API credentials
|
||||||
|
_, err := clientGetWithAuth(ts.URL+"/api/v1/messages", "uiuser", "uipass")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected regular API to work with UI credentials, got error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular API endpoint should fail with Send API credentials
|
||||||
|
_, err = clientGetWithAuth(ts.URL+"/api/v1/messages", "senduser", "sendpass")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected regular API to fail with Send API credentials")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
logger.NoLogging = true
|
logger.NoLogging = true
|
||||||
config.MaxMessages = 0
|
config.MaxMessages = 0
|
||||||
@@ -521,6 +686,59 @@ func clientPost(url, body string) ([]byte, error) {
|
|||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clientPostWithAuth(url, body, username, password string) ([]byte, error) {
|
||||||
|
client := new(http.Client)
|
||||||
|
|
||||||
|
b := strings.NewReader(body)
|
||||||
|
req, err := http.NewRequest("POST", url, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientGetWithAuth(url, username, password string) ([]byte, error) {
|
||||||
|
client := new(http.Client)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
||||||
if a == b {
|
if a == b {
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user