diff --git a/plugin/server/api.go b/plugin/server/api.go index 1bb97d8..bc025d2 100644 --- a/plugin/server/api.go +++ b/plugin/server/api.go @@ -16,19 +16,40 @@ import ( func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { path := r.URL.Path - // Auth middleware: validate shared secret for write operations. - // Read-only endpoints (GET /sessions, GET /health) are accessible to any - // authenticated Mattermost user — no shared secret required. + // Auth middleware: two auth paths. + // 1. Shared secret (Bearer token) — used by the daemon for write operations. + // 2. Mattermost session (Mattermost-User-Id header) — used by browser requests. + // The Mattermost server automatically injects this header for authenticated + // requests routed through the plugin HTTP handler. + // + // Read-only endpoints (GET /sessions, GET /health) accept either auth method. + // Write endpoints (POST, PUT, DELETE) require the shared secret. isReadOnly := r.Method == http.MethodGet && (path == "/api/v1/sessions" || path == "/api/v1/health") - if !isReadOnly { - config := p.getConfiguration() - if config.SharedSecret != "" { - auth := r.Header.Get("Authorization") - expected := "Bearer " + config.SharedSecret - if auth != expected { - http.Error(w, `{"error": "unauthorized"}`, http.StatusUnauthorized) - return - } + + config := p.getConfiguration() + hasSharedSecret := false + if config.SharedSecret != "" { + auth := r.Header.Get("Authorization") + expected := "Bearer " + config.SharedSecret + hasSharedSecret = (auth == expected) + } + + // Check Mattermost session auth (browser requests). + // The MM server injects Mattermost-User-Id for authenticated users. + mmUserID := r.Header.Get("Mattermost-User-Id") + hasMattermostSession := mmUserID != "" + + if isReadOnly { + // Read-only: accept either shared secret OR Mattermost session + if !hasSharedSecret && !hasMattermostSession { + http.Error(w, `{"error": "unauthorized: valid Mattermost session or shared secret required"}`, http.StatusUnauthorized) + return + } + } else { + // Write operations: require shared secret (daemon auth) + if !hasSharedSecret { + http.Error(w, `{"error": "unauthorized"}`, http.StatusUnauthorized) + return } }