1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package server
16
17 import (
18 "context"
19 "net/http"
20
21 "github.com/go-chi/chi/v5"
22 "gitlab.com/flimzy/httpe"
23
24 "github.com/go-kivik/kivik/v4"
25 internal "github.com/go-kivik/kivik/v4/int/errors"
26 "github.com/go-kivik/kivik/v4/x/server/auth"
27 )
28
29 type contextKey struct{ name string }
30
31 var userContextKey = &contextKey{"userCtx"}
32
33 func userFromContext(ctx context.Context) *auth.UserContext {
34 user, _ := ctx.Value(userContextKey).(*auth.UserContext)
35 return user
36 }
37
38 type authService struct {
39 s *Server
40 }
41
42 var _ auth.Server = (*authService)(nil)
43
44
45 func (s *authService) UserStore() auth.UserStore {
46 return s.s.userStores
47 }
48
49 func (s *authService) Bind(r *http.Request, v interface{}) error {
50 return s.s.bind(r, v)
51 }
52
53 type doneWriter struct {
54 http.ResponseWriter
55 done bool
56 }
57
58 func (w *doneWriter) WriteHeader(status int) {
59 w.done = true
60 w.ResponseWriter.WriteHeader(status)
61 }
62
63 func (w *doneWriter) Write(b []byte) (int, error) {
64 w.done = true
65 return w.ResponseWriter.Write(b)
66 }
67
68
69 func (s *Server) authMiddleware(next httpe.HandlerWithError) httpe.HandlerWithError {
70 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
71 ctx := r.Context()
72 if len(s.authFuncs) == 0 {
73
74 r = r.WithContext(context.WithValue(ctx, userContextKey, &auth.UserContext{
75 Name: "admin",
76 Roles: []string{auth.RoleAdmin},
77 }))
78 return next.ServeHTTPWithError(w, r)
79 }
80
81 dw := &doneWriter{ResponseWriter: w}
82
83 var userCtx *auth.UserContext
84 var err error
85 for _, authFunc := range s.authFuncs {
86 userCtx, err = authFunc(dw, r)
87 if err != nil {
88 return err
89 }
90 if dw.done {
91 return nil
92 }
93 if userCtx != nil {
94 break
95 }
96 }
97 r = r.WithContext(context.WithValue(ctx, userContextKey, userCtx))
98 return next.ServeHTTPWithError(w, r)
99 })
100 }
101
102
103
104 func adminRequired(next httpe.HandlerWithError) httpe.HandlerWithError {
105 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
106 userCtx, _ := r.Context().Value(userContextKey).(*auth.UserContext)
107 if userCtx == nil {
108 return &internal.Error{Status: http.StatusUnauthorized, Message: "User not authenticated"}
109 }
110 if !userCtx.HasRole(auth.RoleAdmin) {
111 return &internal.Error{Status: http.StatusForbidden, Message: "Admin privileges required"}
112 }
113 return next.ServeHTTPWithError(w, r)
114 })
115 }
116
117 func (s *Server) dbMembershipRequired(next httpe.HandlerWithError) httpe.HandlerWithError {
118 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
119 db := chi.URLParam(r, "db")
120 security, err := s.client.DB(db).Security(r.Context())
121 if err != nil {
122 return &internal.Error{Status: http.StatusBadGateway, Err: err}
123 }
124
125 if err := validateDBMembership(userFromContext(r.Context()), security); err != nil {
126 return err
127 }
128
129 return next.ServeHTTPWithError(w, r)
130 })
131 }
132
133
134
135
136
137
138 func validateDBMembership(user *auth.UserContext, security *kivik.Security) error {
139
140 if len(security.Members.Names) == 0 && len(security.Members.Roles) == 0 {
141 return nil
142 }
143
144 if user == nil {
145 return &internal.Error{Status: http.StatusUnauthorized, Message: "User not authenticated"}
146 }
147
148 for _, name := range security.Members.Names {
149 if name == user.Name {
150 return nil
151 }
152 }
153 for _, role := range security.Members.Roles {
154 if user.HasRole(role) {
155 return nil
156 }
157 }
158 for _, name := range security.Admins.Names {
159 if name == user.Name {
160 return nil
161 }
162 }
163 for _, role := range security.Admins.Roles {
164 if user.HasRole(role) {
165 return nil
166 }
167 }
168 if user.HasRole(auth.RoleAdmin) {
169 return nil
170 }
171 return &internal.Error{Status: http.StatusForbidden, Message: "User lacks sufficient privileges"}
172 }
173
174 func (s *Server) dbAdminRequired(next httpe.HandlerWithError) httpe.HandlerWithError {
175 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
176 db := chi.URLParam(r, "db")
177 security, err := s.client.DB(db).Security(r.Context())
178 if err != nil {
179 return &internal.Error{Status: http.StatusBadGateway, Err: err}
180 }
181
182 if err := validateDBAdmin(userFromContext(r.Context()), security); err != nil {
183 return err
184 }
185
186 return next.ServeHTTPWithError(w, r)
187 })
188 }
189
190
191
192
193
194
195 func validateDBAdmin(user *auth.UserContext, security *kivik.Security) error {
196 if user == nil {
197 return &internal.Error{Status: http.StatusUnauthorized, Message: "User not authenticated"}
198 }
199 for _, name := range security.Admins.Names {
200 if name == user.Name {
201 return nil
202 }
203 }
204 if user.HasRole(auth.RoleAdmin) {
205 return nil
206 }
207 for _, role := range security.Admins.Roles {
208 if user.HasRole(role) {
209 return nil
210 }
211 }
212 return &internal.Error{Status: http.StatusForbidden, Message: "User lacks sufficient privileges"}
213 }
214
View as plain text