File size: 2,232 Bytes
857a91b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package api

import (
	"net/http"
	"strings"
)

// alwaysOpen is the set of endpoints reachable without any credentials:
// liveness/readiness/version probes and the capabilities advert.
func alwaysOpen(path string) bool {
	switch path {
	case "/v1/health", "/v1/ready", "/v1/version", "/v1/capabilities":
		return true
	}
	return false
}

// withAuth authenticates API requests. Two credential types are accepted and
// either is sufficient:
//
//   - a valid user session bearer token (multitenant console), or
//   - the operator API token (self-hosted single-tenant runtime), when one is
//     configured via MATRIX_RUNTIME_API_TOKEN.
//
// Public probes, the user-auth endpoints, runtime onboarding (which carries its
// own join/runtime-token auth), and static console assets are always allowed.
// In production modes a request with no valid credential is rejected
// (fail-closed); in local-dev it is permitted for convenience.
func (s *Server) withAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path

		// Public probes + non-API (console assets).
		if alwaysOpen(path) || !strings.HasPrefix(path, "/v1/") {
			next.ServeHTTP(w, r)
			return
		}
		// User auth endpoints (login/signup/forgot/reset/verify) and runtime
		// onboarding (own-token auth) are always reachable.
		if strings.HasPrefix(path, "/v1/auth/") ||
			path == "/v1/cloud/runtimes/register" || path == "/v1/cloud/runtimes/heartbeat" {
			next.ServeHTTP(w, r)
			return
		}

		token := bearer(r)

		// 1) Valid user session → allow (multitenant console).
		if s.store != nil && token != "" {
			if _, err := s.store.UserBySession(token); err == nil {
				next.ServeHTTP(w, r)
				return
			}
		}
		// 2) Operator API token configured → require an exact match.
		if s.cfg.APIToken != "" {
			if token == s.cfg.APIToken {
				next.ServeHTTP(w, r)
				return
			}
			writeError(w, http.StatusUnauthorized, "missing or invalid bearer token")
			return
		}
		// 3) No operator token configured: fail-closed in production, allow in dev.
		if s.cfg.IsProduction() {
			writeError(w, http.StatusUnauthorized, "authentication required")
			return
		}
		next.ServeHTTP(w, r)
	})
}