@devwithbobby/loops 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/prds/SECURITY.md DELETED
@@ -1,246 +0,0 @@
1
- # Security Considerations for Loops Component
2
-
3
- ## Current Security Risks
4
-
5
- ### 1. ⚠️ **No Authentication/Authorization in Example**
6
- **Risk Level: HIGH**
7
-
8
- The example (`example/convex/example.ts`) exports all functions directly without authentication checks. This means:
9
- - Any authenticated user (or anyone with access) can send emails
10
- - Anyone can delete contacts
11
- - Anyone can unsubscribe users
12
- - No permission checks
13
-
14
- **Mitigation Required:**
15
- - Apps MUST wrap component functions with authentication
16
- - Add authorization checks before exposing functions to clients
17
- - Consider role-based access control
18
-
19
- ### 2. ⚠️ **API Key Exposure Risk**
20
- **Risk Level: MEDIUM**
21
-
22
- The API key is passed through function arguments to component actions:
23
- - API keys appear in function arguments (could be logged)
24
- - Error messages might leak API key information
25
- - API key stored in memory in multiple places
26
-
27
- **Current Protection:**
28
- - API key is `private readonly` in client class
29
- - Stored in environment variables (not in code)
30
- - Only accessible from server-side code
31
-
32
- **Recommendations:**
33
- - Avoid logging function arguments
34
- - Sanitize error messages
35
- - Consider using Convex secrets management
36
-
37
- ### 3. ⚠️ **Error Message Information Leakage**
38
- **Risk Level: MEDIUM**
39
-
40
- Error messages could expose sensitive information:
41
- ```typescript
42
- throw new Error(`Loops API error: ${response.status} ${error}`);
43
- ```
44
- - Could leak API details
45
- - Could expose internal structure
46
-
47
- **Recommendation:**
48
- - Sanitize error messages in production
49
- - Log detailed errors server-side only
50
- - Return generic error messages to clients
51
-
52
- ### 4. ⚠️ **No Rate Limiting**
53
- **Risk Level: MEDIUM**
54
-
55
- Functions can be called unlimited times:
56
- - Could be abused to send spam
57
- - Could exhaust API quotas
58
- - Could cause DoS
59
-
60
- **Recommendation:**
61
- - Implement rate limiting in app wrapper functions
62
- - Use Convex rate limiting features
63
- - Consider per-user/per-IP limits
64
-
65
- ### 5. ⚠️ **countContacts Query Exposed**
66
- **Risk Level: LOW-MEDIUM**
67
-
68
- The `countContacts` query can reveal user counts without authentication:
69
- - Anyone with app access can see audience sizes
70
- - Could be used for reconnaissance
71
-
72
- **Recommendation:**
73
- - Wrap in app function with auth
74
- - Consider if counts should be public information
75
-
76
- ### 6. ✅ **Input Validation**
77
- **Status: GOOD**
78
-
79
- - Zod validation on all inputs
80
- - Email validation using `.email()` validator
81
- - Type safety enforced
82
-
83
- **Remaining Concerns:**
84
- - Ensure no email injection attacks
85
- - Validate event names and properties
86
-
87
- ### 7. ✅ **Component Isolation**
88
- **Status: GOOD**
89
-
90
- - Component functions are internal (not directly callable from clients)
91
- - Apps must explicitly wrap functions
92
- - Follows Convex security model
93
-
94
- ## Security Best Practices
95
-
96
- ### ✅ What to Do:
97
-
98
- 1. **Always Add Authentication in App Wrapper:**
99
- ```typescript
100
- // ✅ GOOD - Add auth check
101
- export const addContact = action({
102
- args: { email: v.string(), ... },
103
- handler: async (ctx, args) => {
104
- const identity = await ctx.auth.getUserIdentity();
105
- if (!identity) throw new Error("Unauthorized");
106
-
107
- // Check permissions
108
- if (!hasPermission(identity, "manageContacts")) {
109
- throw new Error("Forbidden");
110
- }
111
-
112
- return await loops.addContact(ctx, { email: args.email, ... });
113
- },
114
- });
115
- ```
116
-
117
- 2. **Use Environment Variables for API Key:**
118
- ```typescript
119
- // ✅ GOOD - API key from environment
120
- // Set LOOPS_API_KEY in Convex environment variables first:
121
- // npx convex env set LOOPS_API_KEY "your-api-key"
122
- const loops = new Loops(components.loopsComponent);
123
- // Uses process.env.LOOPS_API_KEY automatically
124
-
125
- // ❌ BAD - Never pass API key directly in production
126
- // const loops = new Loops(components.loopsComponent, { apiKey: "..." });
127
- ```
128
- **📖 See [ENV_SETUP.md](./ENV_SETUP.md) for detailed setup instructions and security best practices.**
129
-
130
- 3. **Sanitize Error Messages:**
131
- ```typescript
132
- // ✅ GOOD - Generic error to client
133
- try {
134
- await loops.addContact(ctx, contact);
135
- } catch (error) {
136
- // Log detailed error server-side
137
- console.error("Failed to add contact:", error);
138
- // Return generic error to client
139
- throw new Error("Failed to add contact. Please try again.");
140
- }
141
- ```
142
-
143
- 4. **Implement Rate Limiting:**
144
- ```typescript
145
- import { rateLimit } from "convex-helpers/server/rateLimit";
146
-
147
- export const sendTransactional = action({
148
- args: { ... },
149
- handler: async (ctx, args) => {
150
- await rateLimit(ctx, { maxOps: 10, period: 60000 }); // 10 per minute
151
- return await loops.sendTransactional(ctx, args);
152
- },
153
- });
154
- ```
155
-
156
- 5. **Validate User Permissions:**
157
- ```typescript
158
- export const deleteContact = action({
159
- args: { email: v.string() },
160
- handler: async (ctx, args) => {
161
- const identity = await ctx.auth.getUserIdentity();
162
- if (!identity) throw new Error("Unauthorized");
163
-
164
- // Only admins can delete contacts
165
- if (!isAdmin(identity)) {
166
- throw new Error("Only admins can delete contacts");
167
- }
168
-
169
- return await loops.deleteContact(ctx, args.email);
170
- },
171
- });
172
- ```
173
-
174
- ### ❌ What NOT to Do:
175
-
176
- 1. **Don't Export Functions Directly Without Auth:**
177
- ```typescript
178
- // ❌ BAD - No authentication
179
- export const { addContact, deleteContact } = loops.api();
180
- ```
181
-
182
- 2. **Don't Pass API Key from Client:**
183
- ```typescript
184
- // ❌ BAD - Never do this
185
- export const addContact = action({
186
- args: { apiKey: v.string(), ... }, // Never accept API key from client
187
- handler: async (ctx, args) => { ... }
188
- });
189
- ```
190
-
191
- 3. **Don't Expose Internal Errors:**
192
- ```typescript
193
- // ❌ BAD - Exposes internal details
194
- catch (error) {
195
- throw new Error(`API call failed: ${error.message} with key ${apiKey}`);
196
- }
197
- ```
198
-
199
- 4. **Don't Skip Rate Limiting for Email Sending:**
200
- ```typescript
201
- // ❌ BAD - No rate limiting
202
- export const sendCampaign = action({
203
- handler: async (ctx, args) => {
204
- // Anyone could spam unlimited emails!
205
- return await loops.sendCampaign(ctx, args);
206
- },
207
- });
208
- ```
209
-
210
- ## Recommended Security Architecture
211
-
212
- ```
213
- Client (React)
214
-
215
- App Function (with auth, rate limiting, validation)
216
-
217
- Loops Client (with API key from env)
218
-
219
- Component Function (validates input)
220
-
221
- Loops.so API
222
- ```
223
-
224
- ## Security Checklist for Apps Using This Component
225
-
226
- - [ ] Wrap all functions with authentication checks
227
- - [ ] Add authorization checks (role-based if needed)
228
- - [x] Implement rate limiting for email-sending functions - ✅ **IMPLEMENTED** See RATE_LIMITING.md
229
- - [ ] Sanitize error messages before returning to clients
230
- - [x] Store API key in Convex environment variables only - ✅ **IMPLEMENTED** See ENV_SETUP.md
231
- - [ ] Audit who can access contact management functions
232
- - [ ] Consider logging all email operations for audit trail
233
- - [ ] Validate user permissions for sensitive operations (delete, unsubscribe)
234
- - [x] Monitor for unusual activity (spam patterns) - ✅ **IMPLEMENTED** See MONITORING.md
235
-
236
- ## Component-Level Security
237
-
238
- The component itself is secure because:
239
- - ✅ Functions are internal (not directly callable from clients)
240
- - ✅ Input validation with Zod
241
- - ✅ Type safety enforced
242
- - ✅ Component isolation prevents unauthorized access
243
- - ✅ API key never exposed to client code
244
-
245
- However, **apps using this component MUST implement their own security layer** when exposing functions to clients.
246
-