feat: add Mattermost session auth for browser requests
- Add dual auth path in ServeHTTP: shared secret (daemon) OR Mattermost session (browser) - Read-only endpoints (GET /sessions, GET /health) accept either auth method - Write endpoints (POST, PUT, DELETE) still require shared secret - Browser requests authenticated via Mattermost-User-Id header (auto-injected by MM server) - Unauthenticated requests now properly rejected with 401 Fixes: Issue #5 Phase 1 - RHS Panel auth fix
This commit is contained in:
@@ -16,19 +16,40 @@ import (
|
|||||||
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
|
|
||||||
// Auth middleware: validate shared secret for write operations.
|
// Auth middleware: two auth paths.
|
||||||
// Read-only endpoints (GET /sessions, GET /health) are accessible to any
|
// 1. Shared secret (Bearer token) — used by the daemon for write operations.
|
||||||
// authenticated Mattermost user — no shared secret required.
|
// 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")
|
isReadOnly := r.Method == http.MethodGet && (path == "/api/v1/sessions" || path == "/api/v1/health")
|
||||||
if !isReadOnly {
|
|
||||||
config := p.getConfiguration()
|
config := p.getConfiguration()
|
||||||
if config.SharedSecret != "" {
|
hasSharedSecret := false
|
||||||
auth := r.Header.Get("Authorization")
|
if config.SharedSecret != "" {
|
||||||
expected := "Bearer " + config.SharedSecret
|
auth := r.Header.Get("Authorization")
|
||||||
if auth != expected {
|
expected := "Bearer " + config.SharedSecret
|
||||||
http.Error(w, `{"error": "unauthorized"}`, http.StatusUnauthorized)
|
hasSharedSecret = (auth == expected)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user