@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/README.md +343 -375
- package/package.json +2 -2
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -14
- package/prds/CHANGELOG.md +0 -38
- package/prds/CLAUDE.md +0 -408
- package/prds/CONTRIBUTING.md +0 -274
- package/prds/ENV_SETUP.md +0 -222
- package/prds/MONITORING.md +0 -301
- package/prds/RATE_LIMITING.md +0 -412
- package/prds/SECURITY.md +0 -246
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
|
-
|