Skip to main content

gatekpr_patterns/
lib.rs

1//! Shopify App Approver - Pattern Matching Library
2//!
3//! This crate provides a centralized pattern registry and pre-built pattern sets
4//! for validating Shopify apps against approval requirements.
5//!
6//! # Overview
7//!
8//! The crate is organized into:
9//! - [`registry`]: Core pattern registry with compile-once regex management
10//! - [`api`]: Patterns for GraphQL/REST API detection and deprecated endpoints
11//! - [`security`]: Patterns for security vulnerabilities and hardcoded secrets
12//! - [`webhooks`]: Patterns for GDPR webhook compliance
13//! - [`billing`]: Patterns for Shopify Billing API and third-party payment detection
14//!
15//! # Example
16//!
17//! ```rust
18//! use gatekpr_patterns::{
19//!     api::check_api_usage,
20//!     security::find_security_issues,
21//!     webhooks::check_gdpr_webhooks,
22//!     billing::check_billing_compliance,
23//! };
24//!
25//! let code = r#"
26//!     const client = new AdminGraphqlClient();
27//!     const result = await client.query({ data: gql`query { shop { name } }` });
28//! "#;
29//!
30//! let api_status = check_api_usage(code);
31//! assert!(api_status.uses_graphql);
32//!
33//! let security = find_security_issues(code);
34//! assert!(!security.has_issues());
35//! ```
36//!
37//! # Pattern Sets
38//!
39//! Each domain module provides:
40//! - A `Lazy<PatternRegistry>` with pre-compiled patterns
41//! - Pattern key constants for easy reference
42//! - Helper functions for common checks
43//! - Status structs with compliance evaluation
44
45pub mod api;
46pub mod billing;
47pub mod registry;
48pub mod security;
49pub mod webhooks;
50
51// Re-export commonly used types
52pub use api::{check_api_usage, ApiUsageStatus, DEPRECATED_KEYS, GRAPHQL_KEYS, REST_KEYS};
53pub use billing::{
54    check_billing_compliance, BillingStatus, SHOPIFY_BILLING_KEYS, THIRD_PARTY_PAYMENT_KEYS,
55};
56pub use registry::{PatternError, PatternMatch, PatternRegistry, PatternSet, Result};
57pub use security::{
58    find_security_issues, SecretMatch, SecurityIssues, DANGEROUS_FUNCTION_KEYS,
59    HARDCODED_SECRET_KEYS, HTTP_URL_KEYS,
60};
61pub use webhooks::{
62    check_gdpr_webhooks, GdprWebhookStatus, GDPR_WEBHOOK_KEYS, WEBHOOK_SECURITY_KEYS,
63};
64
65/// Run all compliance checks at once
66pub fn check_all_compliance(text: &str) -> ComplianceReport {
67    ComplianceReport {
68        api: check_api_usage(text),
69        security: find_security_issues(text),
70        webhooks: check_gdpr_webhooks(text),
71        billing: check_billing_compliance(text),
72    }
73}
74
75/// Comprehensive compliance report
76#[derive(Debug, Clone, Default)]
77pub struct ComplianceReport {
78    /// API usage status
79    pub api: ApiUsageStatus,
80    /// Security issues found
81    pub security: SecurityIssues,
82    /// GDPR webhook status
83    pub webhooks: GdprWebhookStatus,
84    /// Billing compliance status
85    pub billing: BillingStatus,
86}
87
88impl ComplianceReport {
89    /// Check if all compliance requirements are met
90    pub fn is_fully_compliant(&self) -> bool {
91        self.api.is_compliant()
92            && !self.security.has_issues()
93            && self.webhooks.is_compliant()
94            && (self.billing.is_compliant() || !self.billing.has_billing())
95    }
96
97    /// Get overall status
98    pub fn overall_status(&self) -> &'static str {
99        if self.security.severity() == "critical" {
100            "critical"
101        } else if !self.api.is_compliant()
102            || !self.webhooks.is_compliant()
103            || (self.billing.has_billing() && !self.billing.is_compliant())
104        {
105            "fail"
106        } else if self.security.has_issues() {
107            "warning"
108        } else {
109            "pass"
110        }
111    }
112
113    /// Get a summary of issues
114    pub fn summary(&self) -> Vec<String> {
115        let mut issues = Vec::new();
116
117        // API issues
118        if self.api.uses_rest {
119            issues.push("REST API usage detected (should use GraphQL)".to_string());
120        }
121        for dep in &self.api.deprecated_endpoints {
122            issues.push(format!("Deprecated API: {}", dep));
123        }
124
125        // Security issues
126        for func in &self.security.dangerous_functions {
127            issues.push(format!("Dangerous function: {}", func));
128        }
129        for secret in &self.security.hardcoded_secrets {
130            issues.push(format!(
131                "Potential hardcoded secret: {} ({} occurrences)",
132                secret.pattern, secret.count
133            ));
134        }
135        if self.security.insecure_http {
136            issues.push("Insecure HTTP URLs detected".to_string());
137        }
138
139        // Webhook issues
140        for missing in self.webhooks.missing() {
141            issues.push(format!("Missing GDPR webhook: {}", missing));
142        }
143        if !self.webhooks.has_security() {
144            issues.push("HMAC verification not detected for webhooks".to_string());
145        }
146
147        // Billing issues
148        for provider in &self.billing.third_party_detected {
149            issues.push(format!("Third-party payment provider: {}", provider));
150        }
151
152        issues
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_check_all_compliance() {
162        let compliant_code = r#"
163            // GraphQL API usage
164            const client = new AdminGraphqlClient();
165            const result = await client.query({
166                data: gql`query { products { edges { node { id } } } }`,
167            });
168
169            // GDPR webhooks
170            app.post('/webhooks/customers/data_request', handleDataRequest);
171            app.post('/webhooks/customers/redact', handleCustomerRedact);
172            app.post('/webhooks/shop/redact', handleShopRedact);
173
174            // HMAC verification
175            const hmac = req.headers['X-Shopify-Hmac-SHA256'];
176            verifyHmac(hmac, body);
177
178            // Shopify Billing
179            const subscription = await client.mutate({
180                mutation: appSubscriptionCreate,
181                variables: { name: "Pro Plan" }
182            });
183        "#;
184
185        let report = check_all_compliance(compliant_code);
186        assert!(report.api.uses_graphql);
187        assert!(report.webhooks.is_compliant());
188        assert!(report.webhooks.has_security());
189        assert!(report.billing.uses_shopify_billing);
190        assert!(!report.security.has_issues());
191        assert_eq!(report.overall_status(), "pass");
192    }
193
194    #[test]
195    fn test_non_compliant_code() {
196        let non_compliant = r#"
197            // REST API (deprecated)
198            fetch('/admin/api/2024-01/products.json');
199
200            // Stripe integration (not allowed)
201            const stripe = new Stripe(apiKey);
202
203            // Missing GDPR webhooks
204            // eval usage
205            const result = eval(userInput);
206        "#;
207
208        let report = check_all_compliance(non_compliant);
209        assert!(report.api.uses_rest);
210        assert!(!report.billing.third_party_detected.is_empty());
211        assert!(!report.webhooks.is_compliant());
212        assert!(report.security.has_issues());
213        assert!(!report.is_fully_compliant());
214    }
215
216    #[test]
217    fn test_summary() {
218        let code = r#"
219            fetch('/admin/api/2022-01/products.json');
220            element.innerHTML = userInput;
221        "#;
222
223        let report = check_all_compliance(code);
224        let summary = report.summary();
225
226        assert!(summary.iter().any(|s| s.contains("REST API")));
227        assert!(summary.iter().any(|s| s.contains("Deprecated API")));
228        assert!(summary.iter().any(|s| s.contains("inner_html")));
229        assert!(summary.iter().any(|s| s.contains("Missing GDPR webhook")));
230    }
231}