| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package api |
| 4 | |
| 5 | import ( |
| 6 | "net/http" |
| 7 | |
| 8 | "github.com/tenseleyFlow/shithub/internal/entitlements" |
| 9 | "github.com/tenseleyFlow/shithub/internal/orgs" |
| 10 | orgsdb "github.com/tenseleyFlow/shithub/internal/orgs/sqlc" |
| 11 | "github.com/tenseleyFlow/shithub/internal/web/middleware" |
| 12 | ) |
| 13 | |
| 14 | func (h *Handlers) requireAPIOrgOwner(w http.ResponseWriter, r *http.Request, org orgsdb.Org) bool { |
| 15 | auth := middleware.PATAuthFromContext(r.Context()) |
| 16 | if auth.UserID == 0 { |
| 17 | writeAPIError(w, http.StatusUnauthorized, "unauthenticated") |
| 18 | return false |
| 19 | } |
| 20 | if auth.IsSuspended { |
| 21 | writeAPIError(w, http.StatusForbidden, "account is suspended") |
| 22 | return false |
| 23 | } |
| 24 | if auth.IsSiteAdmin { |
| 25 | return true |
| 26 | } |
| 27 | |
| 28 | odeps := orgs.Deps{Pool: h.d.Pool, Logger: h.d.Logger} |
| 29 | isMember, err := orgs.IsMember(r.Context(), odeps, org.ID, auth.UserID) |
| 30 | if err != nil { |
| 31 | h.d.Logger.ErrorContext(r.Context(), "api: org member check", "org_id", org.ID, "error", err) |
| 32 | writeAPIError(w, http.StatusInternalServerError, "authorization failed") |
| 33 | return false |
| 34 | } |
| 35 | if !isMember { |
| 36 | writeAPIError(w, http.StatusNotFound, "org not found") |
| 37 | return false |
| 38 | } |
| 39 | |
| 40 | isOwner, err := orgs.IsOwner(r.Context(), odeps, org.ID, auth.UserID) |
| 41 | if err != nil { |
| 42 | h.d.Logger.ErrorContext(r.Context(), "api: org owner check", "org_id", org.ID, "error", err) |
| 43 | writeAPIError(w, http.StatusInternalServerError, "authorization failed") |
| 44 | return false |
| 45 | } |
| 46 | if !isOwner { |
| 47 | writeAPIError(w, http.StatusForbidden, "organization owner access required") |
| 48 | return false |
| 49 | } |
| 50 | return true |
| 51 | } |
| 52 | |
| 53 | func (h *Handlers) requireOrgFeature(w http.ResponseWriter, r *http.Request, org orgsdb.Org, feature entitlements.Feature, label string) bool { |
| 54 | decision, err := entitlements.CheckOrgFeature(r.Context(), entitlements.Deps{Pool: h.d.Pool}, org.ID, feature) |
| 55 | if err != nil { |
| 56 | h.d.Logger.ErrorContext(r.Context(), "api: org entitlement check", "org_id", org.ID, "feature", feature, "error", err) |
| 57 | writeAPIError(w, http.StatusInternalServerError, "entitlement check failed") |
| 58 | return false |
| 59 | } |
| 60 | if !decision.Allowed { |
| 61 | banner := decision.UpgradeBanner(label, org.Slug) |
| 62 | writeAPIError(w, banner.StatusCode, banner.Message) |
| 63 | return false |
| 64 | } |
| 65 | return true |
| 66 | } |
| 67 |