Skip to content

Commit c2beaab

Browse files
authored
Merge pull request #42 from tstromberg/main
add env var for tier enforcement
2 parents e79e5c0 + 1a149fc commit c2beaab

File tree

6 files changed

+44
-58
lines changed

6 files changed

+44
-58
lines changed

cmd/server/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ var (
5353
return "*"
5454
}(), "Comma-separated list of allowed webhook event types (use '*' for all, default: '*')")
5555
debugHeaders = flag.Bool("debug-headers", false, "Log request headers for debugging (security warning: may log sensitive data)")
56-
enforceTiers = flag.Bool("enforce-tiers", false, "Enforce GitHub Marketplace tier restrictions (default: false, logs warnings only)")
56+
enforceTiers = flag.Bool("enforce-tiers", func() bool {
57+
if val := os.Getenv("ENFORCE_TIERS"); val != "" {
58+
return val == "true" || val == "1"
59+
}
60+
return false
61+
}(), "Enforce GitHub Marketplace tier restrictions (default: false, logs warnings only; can set via ENFORCE_TIERS env)")
5762
)
5863

5964
//nolint:funlen,gocognit,lll,revive,maintidx // Main function orchestrates entire server setup and cannot be split without losing clarity

pkg/github/mock.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import (
88

99
// MockClient is a mock GitHub API client for testing.
1010
// Thread-safe for concurrent access.
11+
//
12+
//nolint:govet // fieldalignment: minimal impact, current order is logical
1113
type MockClient struct {
14+
Orgs []string
1215
Err error
1316
Username string
1417
LastValidatedOrg string
15-
Orgs []string
1618
Tier Tier // Mock tier to return
19+
mu sync.Mutex
1720
UserAndOrgsCalls int
1821
ValidateOrgMembershipCalls int
1922
UserTierCalls int
20-
mu sync.Mutex
2123
}
2224

2325
// UserAndOrgs returns the mock user info.

pkg/github/tier.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,21 @@ func (c *Client) UserTier(ctx context.Context, username string) (Tier, error) {
9595
return TierFree, fmt.Errorf("failed to parse marketplace response: %w", err)
9696
}
9797

98-
tier := mapPlanToTier(account.Plan.Name)
98+
// Map plan name to tier (update when marketplace listing is approved)
99+
var tier Tier
100+
switch strings.ToLower(account.Plan.Name) {
101+
case "pro":
102+
tier = TierPro
103+
case "flock", "team", "enterprise":
104+
tier = TierFlock
105+
default:
106+
tier = TierFree
107+
}
108+
99109
c.logger.Info("marketplace tier detected",
100110
"username", username,
101111
"plan", account.Plan.Name,
102112
"tier", tier)
103113

104114
return tier, nil
105115
}
106-
107-
// mapPlanToTier maps GitHub Marketplace plan names to internal tier constants.
108-
// Update this function when your marketplace listing is approved with the actual plan names.
109-
func mapPlanToTier(planName string) Tier {
110-
switch strings.ToLower(planName) {
111-
case "pro":
112-
return TierPro
113-
case "flock", "team", "enterprise":
114-
return TierFlock
115-
default:
116-
// Unknown plan names default to free tier
117-
return TierFree
118-
}
119-
}

pkg/github/tier_cache.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ import (
77

88
// TierCache caches tier lookups to reduce API calls to GitHub Marketplace.
99
// Implements thread-safe in-memory caching with TTL expiration.
10+
//
11+
//nolint:govet // fieldalignment: minimal impact, current order is logical
1012
type TierCache struct {
1113
mu sync.RWMutex
1214
cache map[string]*tierEntry
13-
ttl time.Duration
1415
stopCh chan struct{}
1516
stopped chan struct{}
17+
ttl time.Duration
1618
}
1719

1820
// tierEntry represents a cached tier with expiration.
1921
type tierEntry struct {
20-
tier Tier
2122
expiresAt time.Time
23+
tier Tier
2224
}
2325

2426
// NewTierCache creates a new tier cache with the specified TTL.

pkg/srv/client.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@ import (
4444
// 4. defer closeWebSocket() closes the WebSocket connection only
4545
// 5. Client.Run() sees context cancellation, exits, calls defer client.Close()
4646
// 6. Hub.Run() processes unregister, calls client.Close() (idempotent via sync.Once)
47+
//
48+
//nolint:govet // fieldalignment: minimal impact, current order is logical
4749
type Client struct {
50+
subscription Subscription
51+
ID string
52+
tier github.Tier // GitHub Marketplace tier
4853
conn *websocket.Conn
4954
send chan Event
5055
control chan map[string]any // Control messages (pongs, shutdown notices)
5156
hub *Hub
5257
done chan struct{}
5358
userOrgs map[string]bool
54-
ID string
55-
subscription Subscription
56-
tier github.Tier // GitHub Marketplace tier
5759
closeOnce sync.Once
5860
closed uint32 // Atomic flag: 1 if closed, 0 if open
5961
}

pkg/srv/subscription.go

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -244,34 +244,19 @@ func matchesPRSubscription(sub Subscription, payload map[string]any, eventOrg st
244244
return false
245245
}
246246

247-
// repoInfo holds repository metadata extracted from event payloads.
248-
type repoInfo struct {
249-
Private bool
250-
FullName string
251-
}
252-
253-
// extractRepoFromPayload extracts repository information from a GitHub webhook payload.
254-
// Returns nil if no repository information is found in the payload.
255-
func extractRepoFromPayload(payload map[string]any) *repoInfo {
247+
// extractRepoInfo extracts repository private status and full name from webhook payload.
248+
// Returns (private, fullName, ok) where ok is false if repository data is missing.
249+
//
250+
//nolint:errcheck,revive // Type assertions intentionally unchecked; defaults are correct
251+
func extractRepoInfo(payload map[string]any) (private bool, fullName string, ok bool) {
256252
repoData, ok := payload["repository"].(map[string]any)
257253
if !ok {
258-
return nil
259-
}
260-
261-
private, ok := repoData["private"].(bool)
262-
if !ok {
263-
private = false // Default to false if not present
254+
return false, "", false
264255
}
265256

266-
fullName, ok := repoData["full_name"].(string)
267-
if !ok {
268-
fullName = "" // Default to empty string if not present
269-
}
270-
271-
return &repoInfo{
272-
Private: private,
273-
FullName: fullName,
274-
}
257+
private, _ = repoData["private"].(bool)
258+
fullName, _ = repoData["full_name"].(string)
259+
return private, fullName, true
275260
}
276261

277262
// matchesForTest is a test helper that creates a minimal client for testing.
@@ -294,21 +279,15 @@ func matches(ctx context.Context, client *Client, event Event, payload map[strin
294279
sub := client.subscription
295280
userOrgs := client.userOrgs
296281

297-
// Extract repository info early to check private repo access
298-
repo := extractRepoFromPayload(payload)
299-
300282
// Filter private repo events based on tier
301-
if repo != nil && repo.Private {
302-
canAccess := client.CanAccessPrivateRepos()
303-
enforceTiers := client.hub.enforceTiers
304-
305-
if !canAccess {
306-
if enforceTiers {
283+
if private, repoName, ok := extractRepoInfo(payload); ok && private {
284+
if !client.CanAccessPrivateRepos() {
285+
if client.hub.enforceTiers {
307286
// Enforcement is active - actually filter the event
308287
logger.Info(ctx, "filtering private repo event (tier enforcement active)", logger.Fields{
309288
"user": sub.Username,
310289
"tier": string(client.tier),
311-
"repo": repo.FullName,
290+
"repo": repoName,
312291
"event_type": event.Type,
313292
})
314293
return false
@@ -317,7 +296,7 @@ func matches(ctx context.Context, client *Client, event Event, payload map[strin
317296
logger.Warn(ctx, "would filter private repo event if enforcement was active", logger.Fields{
318297
"user": sub.Username,
319298
"tier": string(client.tier),
320-
"repo": repo.FullName,
299+
"repo": repoName,
321300
"event_type": event.Type,
322301
"suggestion": "upgrade to Pro or Flock tier for private repo access",
323302
})

0 commit comments

Comments
 (0)