@squidcloud/cli 1.0.446 → 1.0.447

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.
@@ -0,0 +1,356 @@
1
+ # Security Rules
2
+
3
+ Squid provides a comprehensive security model using backend decorators. Every operation can be secured using security rules - backend functions that contain business logic to determine whether an operation is allowed.
4
+
5
+ ## Contents
6
+ - Overview
7
+ - Auth Methods
8
+ - Database Security
9
+ - Queue Security
10
+ - Storage Security
11
+ - API Security
12
+ - Native Query Security
13
+ - AI Security
14
+ - Distributed Lock Security
15
+ - GraphQL Security
16
+ - Authentication Patterns
17
+ - Best Practices
18
+
19
+ ## Overview
20
+
21
+ Security decorators are backend-only decorators that define who can access your data and resources. They:
22
+ - Return a boolean (or `Promise<boolean>`) indicating whether the operation is allowed
23
+ - Give developers full control to implement any business logic needed for authorization
24
+ - Are required when clients don't use an API key
25
+
26
+ **Important:** Clients initialized with an API key **bypass all security rules**. Security rules only apply to clients using authentication providers.
27
+
28
+ ## Auth Methods
29
+
30
+ These methods are available in all security rule functions via `this`:
31
+
32
+ ```typescript
33
+ // Get user authentication (JWT token)
34
+ this.getUserAuth() // AuthWithBearer | undefined
35
+ // Returns: { type: 'Bearer', userId: string, expiration: number, attributes: Record<string, any> }
36
+
37
+ // Get API key authentication
38
+ this.getApiKeyAuth() // AuthWithApiKey | undefined
39
+ // Returns: { type: 'ApiKey', apiKey: string }
40
+
41
+ this.isAuthenticated() // boolean - true if user token OR API key
42
+ this.assertIsAuthenticated() // throws if not authenticated
43
+ this.assertApiKeyCall() // throws if not API key auth
44
+ ```
45
+
46
+ ## Database Security
47
+
48
+ ### @secureDatabase
49
+
50
+ Secures operations across an entire database integration.
51
+
52
+ ```typescript
53
+ import { SquidService, secureDatabase, QueryContext, MutationContext } from '@squidcloud/backend';
54
+
55
+ export class MyService extends SquidService {
56
+ @secureDatabase('read')
57
+ allowRead(context: QueryContext<User>): boolean {
58
+ const userId = this.getUserAuth()?.userId;
59
+ if (!userId) return false;
60
+ return context.isSubqueryOf('userId', '==', userId);
61
+ }
62
+
63
+ @secureDatabase('write')
64
+ allowWrite(context: MutationContext<User>): boolean {
65
+ return this.isAuthenticated();
66
+ }
67
+
68
+ @secureDatabase('all')
69
+ allowAll(): boolean {
70
+ return this.isAuthenticated();
71
+ }
72
+
73
+ // Secure specific integration
74
+ @secureDatabase('insert', 'my-integration-id')
75
+ allowInsertInIntegration(context: MutationContext): boolean {
76
+ return context.after?.createdBy === 'admin';
77
+ }
78
+ }
79
+ ```
80
+
81
+ **Operation Types:** `'read'`, `'write'`, `'update'`, `'insert'`, `'delete'`, `'all'`
82
+
83
+ ### @secureCollection
84
+
85
+ Secures operations on a specific collection.
86
+
87
+ ```typescript
88
+ // QueryContext for 'read' operations
89
+ @secureCollection('users', 'read')
90
+ allowUserRead(context: QueryContext): boolean {
91
+ const userId = this.getUserAuth()?.userId;
92
+ if (!userId) return false;
93
+ // Check if query filters on a specific field
94
+ return context.isSubqueryOf('id', '==', userId);
95
+ }
96
+
97
+ // MutationContext for 'insert', 'update', 'delete' operations
98
+ @secureCollection('users', 'update')
99
+ allowUserUpdate(context: MutationContext<User>): boolean {
100
+ const userId = this.getUserAuth()?.userId;
101
+ if (!userId) return false;
102
+
103
+ // context.before: document state before mutation
104
+ // context.after: document state after mutation
105
+
106
+ // Check if specific paths were affected
107
+ if (context.affectsPath('email')) {
108
+ return false; // Don't allow email changes
109
+ }
110
+
111
+ // Check ownership
112
+ return context.after?.id === userId;
113
+ }
114
+ ```
115
+
116
+ ### Context Types
117
+
118
+ **QueryContext<T>** - Used for 'read' and 'all' operations:
119
+ ```typescript
120
+ context.collectionName // Name of the collection being queried
121
+ context.integrationId // Integration ID
122
+ context.limit // Query limit
123
+ context.isSubqueryOf(field, operator, value) // Check if query filters on field
124
+ ```
125
+
126
+ **MutationContext<T>** - Used for 'insert', 'update', 'delete' operations:
127
+ ```typescript
128
+ context.before // Document before mutation (undefined for insert)
129
+ context.after // Document after mutation (undefined for delete)
130
+ context.affectsPath(path) // Check if specific field path was modified
131
+ ```
132
+
133
+ Both types are exported from `@squidcloud/backend`.
134
+
135
+ ## Queue Security
136
+
137
+ ### @secureTopic
138
+
139
+ Secures queue topics.
140
+
141
+ ```typescript
142
+ import { SquidService, secureTopic, TopicReadContext, TopicWriteContext } from '@squidcloud/backend';
143
+
144
+ // Read security
145
+ @secureTopic('notifications', 'read')
146
+ allowRead(context: TopicReadContext): boolean {
147
+ return this.isAuthenticated();
148
+ }
149
+
150
+ // Write security with message validation
151
+ @secureTopic('notifications', 'write')
152
+ allowWrite(context: TopicWriteContext<Message>): boolean {
153
+ return context.messages.length <= 100;
154
+ }
155
+
156
+ // For Kafka/Confluent integrations, specify integrationId as third parameter
157
+ @secureTopic('events', 'read', 'kafka-integration')
158
+ allowKafkaRead(context: TopicReadContext): boolean {
159
+ return this.isAuthenticated();
160
+ }
161
+ ```
162
+
163
+ **Types:** `'read'`, `'write'`, `'all'`
164
+
165
+ **Context Types:**
166
+ - `TopicReadContext` - contains `integrationId`, `topicName`
167
+ - `TopicWriteContext<T>` - contains `integrationId`, `topicName`, `messages` array
168
+
169
+ ## Storage Security
170
+
171
+ ### @secureStorage
172
+
173
+ Secures storage operations.
174
+
175
+ ```typescript
176
+ import { SquidService, secureStorage, StorageContext } from '@squidcloud/backend';
177
+
178
+ @secureStorage('write')
179
+ allowWrite(context: StorageContext): boolean {
180
+ return !context.pathsInBucket.some(p => p.startsWith('/admin'));
181
+ }
182
+
183
+ @secureStorage('read', 'my-s3-integration')
184
+ allowRead(context: StorageContext): boolean {
185
+ return this.isAuthenticated();
186
+ }
187
+ ```
188
+
189
+ **Types:** `'read'`, `'write'`, `'update'`, `'insert'`, `'delete'`, `'all'`
190
+
191
+ ## API Security
192
+
193
+ ### @secureApi
194
+
195
+ Secures API integration calls.
196
+
197
+ ```typescript
198
+ import { SquidService, secureApi, ApiCallContext } from '@squidcloud/backend';
199
+
200
+ // Secure specific endpoint
201
+ @secureApi('stripe-api', 'create-charge')
202
+ allowCharge(context: ApiCallContext): boolean {
203
+ const amount = (context.body as any)?.amount;
204
+ return amount < 10000;
205
+ }
206
+
207
+ // Secure entire API integration
208
+ @secureApi('external-api')
209
+ allowApi(): boolean {
210
+ return this.isAuthenticated();
211
+ }
212
+ ```
213
+
214
+ ## Native Query Security
215
+
216
+ ### @secureNativeQuery
217
+
218
+ Secures native database queries (SQL, MongoDB aggregation, etc.).
219
+
220
+ ```typescript
221
+ import { SquidService, secureNativeQuery, NativeQueryContext } from '@squidcloud/backend';
222
+
223
+ @secureNativeQuery('postgres-db')
224
+ allowQuery(context: NativeQueryContext): boolean {
225
+ if (context.type === 'relational') {
226
+ // Prevent destructive queries
227
+ return !context.query.toUpperCase().includes('DROP');
228
+ }
229
+ return true;
230
+ }
231
+ ```
232
+
233
+ ## AI Security
234
+
235
+ ### @secureAiQuery
236
+
237
+ Secures AI query execution.
238
+
239
+ ```typescript
240
+ import { SquidService, secureAiQuery, AiQueryContext } from '@squidcloud/backend';
241
+
242
+ @secureAiQuery()
243
+ allowAiQuery(context: AiQueryContext): boolean {
244
+ return this.isAuthenticated() && context.prompt.length < 1000;
245
+ }
246
+
247
+ @secureAiQuery('specific-integration')
248
+ allowSpecificAiQuery(context: AiQueryContext): boolean {
249
+ return this.getUserAuth()?.attributes?.role === 'admin';
250
+ }
251
+ ```
252
+
253
+ ### @secureAiAgent
254
+
255
+ Secures AI agent access.
256
+
257
+ ```typescript
258
+ import { SquidService, secureAiAgent, SecureAiAgentContext } from '@squidcloud/backend';
259
+
260
+ // Secure specific agent
261
+ @secureAiAgent('customer-bot')
262
+ allowAgent(context: SecureAiAgentContext): boolean {
263
+ return !context.prompt?.includes('admin');
264
+ }
265
+
266
+ // Secure all agents
267
+ @secureAiAgent()
268
+ allowAllAgents(): boolean {
269
+ return this.isAuthenticated();
270
+ }
271
+ ```
272
+
273
+ ## Distributed Lock Security
274
+
275
+ ### @secureDistributedLock
276
+
277
+ Secures distributed lock access.
278
+
279
+ ```typescript
280
+ import { SquidService, secureDistributedLock, DistributedLockContext } from '@squidcloud/backend';
281
+
282
+ // Secure specific mutex
283
+ @secureDistributedLock('payment-processing')
284
+ allowPaymentLock(context: DistributedLockContext): boolean {
285
+ return this.isAuthenticated() && context.mutex === 'payment-processing';
286
+ }
287
+
288
+ // Secure all mutexes
289
+ @secureDistributedLock()
290
+ allowAllLocks(context: DistributedLockContext): boolean {
291
+ return this.isAuthenticated();
292
+ }
293
+ ```
294
+
295
+ **Important:** Without API key, you must define `@secureDistributedLock` decorators to allow lock acquisition.
296
+
297
+ ## GraphQL Security
298
+
299
+ ### @secureGraphQL
300
+
301
+ Secures GraphQL operations.
302
+
303
+ ```typescript
304
+ import { SquidService, secureGraphQL, GraphqlContext } from '@squidcloud/backend';
305
+
306
+ @secureGraphQL('my-graphql-api')
307
+ allowGraphQL(context: GraphqlContext): boolean {
308
+ return this.isAuthenticated();
309
+ }
310
+ ```
311
+
312
+ **Context:** `GraphqlContext` provides the full context including `query`, `variables`, `operationName`, and `isGraphiQL` (boolean indicating if request is from GraphiQL IDE).
313
+
314
+ ## Authentication Patterns
315
+
316
+ ### Row-Level Security Example
317
+
318
+ ```typescript
319
+ @secureCollection('documents', 'read')
320
+ allowDocumentRead(context: QueryContext<Document>): boolean {
321
+ const userId = this.getUserAuth()?.userId;
322
+ if (!userId) return false;
323
+
324
+ // Users can only read documents they own or that are public
325
+ return context.isSubqueryOf('ownerId', '==', userId) ||
326
+ context.isSubqueryOf('isPublic', '==', true);
327
+ }
328
+
329
+ @secureCollection('documents', 'update')
330
+ allowDocumentUpdate(context: MutationContext<Document>): boolean {
331
+ const userId = this.getUserAuth()?.userId;
332
+ if (!userId) return false;
333
+
334
+ // Only owner can update
335
+ return context.before?.ownerId === userId;
336
+ }
337
+ ```
338
+
339
+ ### Role-Based Access Example
340
+
341
+ ```typescript
342
+ @secureCollection('admin-data', 'read')
343
+ allowAdminRead(): boolean {
344
+ const role = this.getUserAuth()?.attributes?.role;
345
+ return role === 'admin' || role === 'superadmin';
346
+ }
347
+ ```
348
+
349
+ ## Best Practices
350
+
351
+ 1. **Always secure collections/topics/storage** - Default to deny
352
+ 2. **Validate input in executables** - Check auth and params
353
+ 3. **Use `isSubqueryOf()` for read rules** - Ensures queries are properly filtered
354
+ 4. **Use `affectsPath()` for write rules** - Control which fields can be modified
355
+ 5. **Never trust client data** - Always validate in security rules
356
+ 6. **Test security rules thoroughly** - Use different user contexts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squidcloud/cli",
3
- "version": "1.0.446",
3
+ "version": "1.0.447",
4
4
  "description": "The Squid CLI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@squidcloud/local-backend": "^1.0.446",
31
+ "@squidcloud/local-backend": "^1.0.447",
32
32
  "adm-zip": "^0.5.16",
33
33
  "copy-webpack-plugin": "^12.0.2",
34
34
  "decompress": "^4.2.1",