package server import ( "context" "net/http" "strings" "task/internal/sqlite" ) type ctxKey int const ( userKey ctxKey = 1 tokenAuthKey ctxKey = 2 ) func (s *Server) currentUser(r *http.Request) (sqlite.User, bool) { u, ok := r.Context().Value(userKey).(sqlite.User) return u, ok } func (s *Server) currentTokenAuth(r *http.Request) (sqlite.TokenAuth, bool) { t, ok := r.Context().Value(tokenAuthKey).(sqlite.TokenAuth) return t, ok } func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, ok := s.authenticate(r) if !ok { writeError(w, http.StatusUnauthorized, "unauthorized") return } next(w, r.WithContext(ctx)) } } func (s *Server) authenticate(r *http.Request) (context.Context, bool) { auth := r.Header.Get("Authorization") if strings.HasPrefix(strings.ToLower(auth), "bearer ") { raw := strings.TrimSpace(auth[len("Bearer "):]) ta, err := s.DB.ResolveBearerToken(raw) if err != nil { return r.Context(), false } ctx := context.WithValue(r.Context(), userKey, ta.User) ctx = context.WithValue(ctx, tokenAuthKey, ta) return ctx, true } c, err := r.Cookie(sessionCookie) if err != nil || c.Value == "" { return r.Context(), false } u, err := s.DB.UserFromSession(c.Value) if err != nil { return r.Context(), false } u.PasswordHash = "" return context.WithValue(r.Context(), userKey, u), true } func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc { return s.requireAuth(func(w http.ResponseWriter, r *http.Request) { if _, tok := s.currentTokenAuth(r); tok { writeError(w, http.StatusForbidden, "admin required") return } u, _ := s.currentUser(r) if !u.IsAdmin { writeError(w, http.StatusForbidden, "admin required") return } next(w, r) }) } func (s *Server) requireProjectRole(projectID string, minRole string, r *http.Request) (sqlite.User, bool, int) { u, ok := s.currentUser(r) if !ok { return sqlite.User{}, false, http.StatusUnauthorized } if ta, ok := s.currentTokenAuth(r); ok { if ta.ProjectID != projectID { return u, false, http.StatusForbidden } need := sqlite.TokenScopeRead if minRole == sqlite.RoleMember || minRole == sqlite.RoleAdmin || minRole == sqlite.RoleOwner { need = sqlite.TokenScopeWrite } // viewer → read; member+ → write if minRole == sqlite.RoleViewer { need = sqlite.TokenScopeRead } if !sqlite.TokenAllows(ta.Scope, need) { return u, false, http.StatusForbidden } return u, true, 0 } if u.IsAdmin { return u, true, 0 } role, err := s.DB.MemberRole(projectID, u.ID) if err != nil || role == "" { return u, false, http.StatusForbidden } if !sqlite.RoleAtLeast(role, minRole) { return u, false, http.StatusForbidden } return u, true, 0 }