worker-que 1.0.0
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/DASHBOARD-QUICKSTART.md +278 -0
- package/DASHBOARD.md +556 -0
- package/LICENSE +21 -0
- package/README.md +414 -0
- package/SSL-QUICK-REFERENCE.md +225 -0
- package/SSL.md +516 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/dashboard/index.d.ts +34 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +164 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/service.d.ts +66 -0
- package/dist/dashboard/service.d.ts.map +1 -0
- package/dist/dashboard/service.js +201 -0
- package/dist/dashboard/service.js.map +1 -0
- package/dist/dashboard/views.d.ts +3 -0
- package/dist/dashboard/views.d.ts.map +1 -0
- package/dist/dashboard/views.js +786 -0
- package/dist/dashboard/views.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/job.d.ts +19 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +36 -0
- package/dist/job.js.map +1 -0
- package/dist/sql.d.ts +8 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +57 -0
- package/dist/sql.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +31 -0
- package/dist/utils.js.map +1 -0
- package/dist/worker.d.ts +19 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +99 -0
- package/dist/worker.js.map +1 -0
- package/migrations/schema.sql +26 -0
- package/package.json +105 -0
package/SSL.md
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
# SSL/TLS Configuration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to configure SSL/TLS connections to PostgreSQL with client certificates.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Basic SSL Connection](#basic-ssl-connection)
|
|
8
|
+
- [SSL with Client Certificates](#ssl-with-client-certificates)
|
|
9
|
+
- [Certificate File Formats](#certificate-file-formats)
|
|
10
|
+
- [Environment Variables](#environment-variables)
|
|
11
|
+
- [Common SSL Modes](#common-ssl-modes)
|
|
12
|
+
- [Troubleshooting](#troubleshooting)
|
|
13
|
+
|
|
14
|
+
## Basic SSL Connection
|
|
15
|
+
|
|
16
|
+
### Simple SSL (sslmode=require)
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Client } from 'que-ts';
|
|
20
|
+
|
|
21
|
+
const client = new Client({
|
|
22
|
+
host: 'your-database-host.com',
|
|
23
|
+
port: 5432,
|
|
24
|
+
database: 'your_database',
|
|
25
|
+
user: 'your_user',
|
|
26
|
+
password: 'your_password',
|
|
27
|
+
ssl: {
|
|
28
|
+
rejectUnauthorized: true, // Verify server certificate
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### SSL without Certificate Verification (Development Only)
|
|
34
|
+
|
|
35
|
+
⚠️ **WARNING:** Only use this in development environments!
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const client = new Client({
|
|
39
|
+
host: 'localhost',
|
|
40
|
+
port: 5432,
|
|
41
|
+
database: 'test_db',
|
|
42
|
+
user: 'test_user',
|
|
43
|
+
password: 'test_password',
|
|
44
|
+
ssl: {
|
|
45
|
+
rejectUnauthorized: false, // Skip certificate verification
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## SSL with Client Certificates
|
|
51
|
+
|
|
52
|
+
### Using Certificate Files
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Client } from 'que-ts';
|
|
56
|
+
import * as fs from 'fs';
|
|
57
|
+
import * as path from 'path';
|
|
58
|
+
|
|
59
|
+
const client = new Client({
|
|
60
|
+
host: 'your-database-host.com',
|
|
61
|
+
port: 5432,
|
|
62
|
+
database: 'your_database',
|
|
63
|
+
user: 'your_user',
|
|
64
|
+
password: 'your_password',
|
|
65
|
+
ssl: {
|
|
66
|
+
// Verify the server's certificate
|
|
67
|
+
rejectUnauthorized: true,
|
|
68
|
+
|
|
69
|
+
// Client certificate (.crt or .pem)
|
|
70
|
+
cert: fs.readFileSync(path.join(__dirname, 'certs/client-cert.pem')),
|
|
71
|
+
|
|
72
|
+
// Client private key (.key)
|
|
73
|
+
key: fs.readFileSync(path.join(__dirname, 'certs/client-key.pem')),
|
|
74
|
+
|
|
75
|
+
// CA certificate to verify server
|
|
76
|
+
ca: fs.readFileSync(path.join(__dirname, 'certs/ca-cert.pem')),
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Using Certificate Paths (Alternative)
|
|
82
|
+
|
|
83
|
+
You can also pass file paths directly if using string buffers:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import * as fs from 'fs';
|
|
87
|
+
|
|
88
|
+
const client = new Client({
|
|
89
|
+
host: 'your-database-host.com',
|
|
90
|
+
port: 5432,
|
|
91
|
+
database: 'your_database',
|
|
92
|
+
user: 'your_user',
|
|
93
|
+
password: 'your_password',
|
|
94
|
+
ssl: {
|
|
95
|
+
rejectUnauthorized: true,
|
|
96
|
+
cert: fs.readFileSync('/path/to/client-cert.pem'),
|
|
97
|
+
key: fs.readFileSync('/path/to/client-key.pem'),
|
|
98
|
+
ca: fs.readFileSync('/path/to/ca-cert.pem'),
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Encrypted Private Key
|
|
104
|
+
|
|
105
|
+
If your private key is password-protected:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const client = new Client({
|
|
109
|
+
host: 'your-database-host.com',
|
|
110
|
+
port: 5432,
|
|
111
|
+
database: 'your_database',
|
|
112
|
+
user: 'your_user',
|
|
113
|
+
password: 'your_password',
|
|
114
|
+
ssl: {
|
|
115
|
+
rejectUnauthorized: true,
|
|
116
|
+
cert: fs.readFileSync('./certs/client-cert.pem'),
|
|
117
|
+
key: fs.readFileSync('./certs/client-key-encrypted.pem'),
|
|
118
|
+
ca: fs.readFileSync('./certs/ca-cert.pem'),
|
|
119
|
+
passphrase: 'your-private-key-passphrase', // Passphrase for the key
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Multiple CA Certificates
|
|
125
|
+
|
|
126
|
+
If you need to trust multiple certificate authorities:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const client = new Client({
|
|
130
|
+
host: 'your-database-host.com',
|
|
131
|
+
port: 5432,
|
|
132
|
+
database: 'your_database',
|
|
133
|
+
user: 'your_user',
|
|
134
|
+
password: 'your_password',
|
|
135
|
+
ssl: {
|
|
136
|
+
rejectUnauthorized: true,
|
|
137
|
+
cert: fs.readFileSync('./certs/client-cert.pem'),
|
|
138
|
+
key: fs.readFileSync('./certs/client-key.pem'),
|
|
139
|
+
ca: [
|
|
140
|
+
fs.readFileSync('./certs/ca-cert-1.pem'),
|
|
141
|
+
fs.readFileSync('./certs/ca-cert-2.pem'),
|
|
142
|
+
],
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Certificate File Formats
|
|
148
|
+
|
|
149
|
+
### Supported Formats
|
|
150
|
+
|
|
151
|
+
- **PEM** (`.pem`, `.crt`, `.cer`, `.key`) - Most common, text-based format
|
|
152
|
+
- **DER** (`.der`) - Binary format (less common)
|
|
153
|
+
|
|
154
|
+
### PEM File Structure
|
|
155
|
+
|
|
156
|
+
A PEM file looks like this:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
-----BEGIN CERTIFICATE-----
|
|
160
|
+
MIIDXTCCAkWgAwIBAgIJAKZ...
|
|
161
|
+
...
|
|
162
|
+
-----END CERTIFICATE-----
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Typical Certificate Files
|
|
166
|
+
|
|
167
|
+
| File | Description | Example Name |
|
|
168
|
+
|------|-------------|--------------|
|
|
169
|
+
| CA Certificate | Certificate Authority (verifies server) | `ca-cert.pem`, `root.crt` |
|
|
170
|
+
| Client Certificate | Your client certificate | `client-cert.pem`, `postgresql.crt` |
|
|
171
|
+
| Client Key | Your private key | `client-key.pem`, `postgresql.key` |
|
|
172
|
+
|
|
173
|
+
### Obtaining Certificates
|
|
174
|
+
|
|
175
|
+
**From PostgreSQL Server:**
|
|
176
|
+
```bash
|
|
177
|
+
# Server certificates are usually in:
|
|
178
|
+
/var/lib/postgresql/data/
|
|
179
|
+
# Or
|
|
180
|
+
/etc/postgresql/<version>/main/
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Generate Self-Signed Certificates (Development):**
|
|
184
|
+
```bash
|
|
185
|
+
# Generate CA key and certificate
|
|
186
|
+
openssl req -new -x509 -days 365 -nodes -text \
|
|
187
|
+
-out ca-cert.pem -keyout ca-key.pem
|
|
188
|
+
|
|
189
|
+
# Generate server key and certificate request
|
|
190
|
+
openssl req -new -nodes -text -out server.csr -keyout server-key.pem
|
|
191
|
+
|
|
192
|
+
# Sign server certificate with CA
|
|
193
|
+
openssl x509 -req -in server.csr -text -days 365 \
|
|
194
|
+
-CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \
|
|
195
|
+
-out server-cert.pem
|
|
196
|
+
|
|
197
|
+
# Generate client key and certificate request
|
|
198
|
+
openssl req -new -nodes -text -out client.csr -keyout client-key.pem
|
|
199
|
+
|
|
200
|
+
# Sign client certificate with CA
|
|
201
|
+
openssl x509 -req -in client.csr -text -days 365 \
|
|
202
|
+
-CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \
|
|
203
|
+
-out client-cert.pem
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Environment Variables
|
|
207
|
+
|
|
208
|
+
### Recommended Production Setup
|
|
209
|
+
|
|
210
|
+
Create a `.env` file:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Database connection
|
|
214
|
+
DB_HOST=your-database-host.com
|
|
215
|
+
DB_PORT=5432
|
|
216
|
+
DB_NAME=your_database
|
|
217
|
+
DB_USER=your_user
|
|
218
|
+
DB_PASSWORD=your_password
|
|
219
|
+
|
|
220
|
+
# SSL Configuration
|
|
221
|
+
DB_SSL_ENABLED=true
|
|
222
|
+
DB_SSL_REJECT_UNAUTHORIZED=true
|
|
223
|
+
DB_SSL_CERT=/path/to/certs/client-cert.pem
|
|
224
|
+
DB_SSL_KEY=/path/to/certs/client-key.pem
|
|
225
|
+
DB_SSL_CA=/path/to/certs/ca-cert.pem
|
|
226
|
+
DB_SSL_PASSPHRASE=your-key-passphrase # Optional
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Using Environment Variables in Code
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { Client } from 'que-ts';
|
|
233
|
+
import * as fs from 'fs';
|
|
234
|
+
import * as dotenv from 'dotenv';
|
|
235
|
+
|
|
236
|
+
dotenv.config();
|
|
237
|
+
|
|
238
|
+
function createClient() {
|
|
239
|
+
const sslConfig = process.env.DB_SSL_ENABLED === 'true' ? {
|
|
240
|
+
rejectUnauthorized: process.env.DB_SSL_REJECT_UNAUTHORIZED !== 'false',
|
|
241
|
+
cert: process.env.DB_SSL_CERT ? fs.readFileSync(process.env.DB_SSL_CERT) : undefined,
|
|
242
|
+
key: process.env.DB_SSL_KEY ? fs.readFileSync(process.env.DB_SSL_KEY) : undefined,
|
|
243
|
+
ca: process.env.DB_SSL_CA ? fs.readFileSync(process.env.DB_SSL_CA) : undefined,
|
|
244
|
+
passphrase: process.env.DB_SSL_PASSPHRASE,
|
|
245
|
+
} : false;
|
|
246
|
+
|
|
247
|
+
return new Client({
|
|
248
|
+
host: process.env.DB_HOST,
|
|
249
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
250
|
+
database: process.env.DB_NAME,
|
|
251
|
+
user: process.env.DB_USER,
|
|
252
|
+
password: process.env.DB_PASSWORD,
|
|
253
|
+
ssl: sslConfig,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const client = createClient();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Common SSL Modes
|
|
261
|
+
|
|
262
|
+
PostgreSQL supports different SSL modes. Here's how to configure them:
|
|
263
|
+
|
|
264
|
+
| SSL Mode | Description | Configuration |
|
|
265
|
+
|----------|-------------|---------------|
|
|
266
|
+
| `disable` | No SSL | `ssl: false` |
|
|
267
|
+
| `prefer` | Try SSL, fallback to non-SSL | Not directly supported (use `require`) |
|
|
268
|
+
| `require` | SSL required, no cert verification | `ssl: { rejectUnauthorized: false }` |
|
|
269
|
+
| `verify-ca` | SSL with CA verification | `ssl: { rejectUnauthorized: true, ca: ... }` |
|
|
270
|
+
| `verify-full` | SSL with full verification | `ssl: { rejectUnauthorized: true, ca: ..., checkServerIdentity: ... }` |
|
|
271
|
+
|
|
272
|
+
### Example: verify-ca Mode
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const client = new Client({
|
|
276
|
+
host: 'your-database-host.com',
|
|
277
|
+
port: 5432,
|
|
278
|
+
database: 'your_database',
|
|
279
|
+
user: 'your_user',
|
|
280
|
+
password: 'your_password',
|
|
281
|
+
ssl: {
|
|
282
|
+
rejectUnauthorized: true,
|
|
283
|
+
ca: fs.readFileSync('./certs/ca-cert.pem'),
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Example: verify-full Mode
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const client = new Client({
|
|
292
|
+
host: 'your-database-host.com',
|
|
293
|
+
port: 5432,
|
|
294
|
+
database: 'your_database',
|
|
295
|
+
user: 'your_user',
|
|
296
|
+
password: 'your_password',
|
|
297
|
+
ssl: {
|
|
298
|
+
rejectUnauthorized: true,
|
|
299
|
+
ca: fs.readFileSync('./certs/ca-cert.pem'),
|
|
300
|
+
// Optional: custom server identity check
|
|
301
|
+
checkServerIdentity: (hostname, cert) => {
|
|
302
|
+
// Custom validation logic
|
|
303
|
+
if (hostname !== 'expected-hostname.com') {
|
|
304
|
+
throw new Error('Hostname mismatch');
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Cloud Provider Examples
|
|
312
|
+
|
|
313
|
+
### AWS RDS
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { Client } from 'que-ts';
|
|
317
|
+
import * as https from 'https';
|
|
318
|
+
|
|
319
|
+
// Download RDS CA bundle from:
|
|
320
|
+
// https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
|
|
321
|
+
|
|
322
|
+
const client = new Client({
|
|
323
|
+
host: 'your-instance.region.rds.amazonaws.com',
|
|
324
|
+
port: 5432,
|
|
325
|
+
database: 'your_database',
|
|
326
|
+
user: 'your_user',
|
|
327
|
+
password: 'your_password',
|
|
328
|
+
ssl: {
|
|
329
|
+
rejectUnauthorized: true,
|
|
330
|
+
ca: fs.readFileSync('./rds-ca-bundle.pem'),
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Google Cloud SQL
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
const client = new Client({
|
|
339
|
+
host: '/cloudsql/project:region:instance', // Unix socket
|
|
340
|
+
// Or use IP with SSL:
|
|
341
|
+
// host: 'your-instance-ip',
|
|
342
|
+
port: 5432,
|
|
343
|
+
database: 'your_database',
|
|
344
|
+
user: 'your_user',
|
|
345
|
+
password: 'your_password',
|
|
346
|
+
ssl: {
|
|
347
|
+
rejectUnauthorized: true,
|
|
348
|
+
ca: fs.readFileSync('./server-ca.pem'),
|
|
349
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
350
|
+
key: fs.readFileSync('./client-key.pem'),
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Azure Database for PostgreSQL
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
const client = new Client({
|
|
359
|
+
host: 'your-server.postgres.database.azure.com',
|
|
360
|
+
port: 5432,
|
|
361
|
+
database: 'your_database',
|
|
362
|
+
user: 'your_user@your-server',
|
|
363
|
+
password: 'your_password',
|
|
364
|
+
ssl: {
|
|
365
|
+
rejectUnauthorized: true,
|
|
366
|
+
// Azure root certificate
|
|
367
|
+
ca: fs.readFileSync('./BaltimoreCyberTrustRoot.crt.pem'),
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Heroku Postgres
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const client = new Client({
|
|
376
|
+
connectionString: process.env.DATABASE_URL,
|
|
377
|
+
ssl: {
|
|
378
|
+
rejectUnauthorized: false, // Heroku uses self-signed certificates
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Troubleshooting
|
|
384
|
+
|
|
385
|
+
### Error: "self signed certificate"
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
Error: self signed certificate
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Solution:** Set `rejectUnauthorized: false` (development only) or add the CA certificate:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
ssl: {
|
|
395
|
+
rejectUnauthorized: true,
|
|
396
|
+
ca: fs.readFileSync('./ca-cert.pem'),
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Error: "unable to verify the first certificate"
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
Error: unable to verify the first certificate
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Solution:** Add the complete certificate chain:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
ssl: {
|
|
410
|
+
rejectUnauthorized: true,
|
|
411
|
+
ca: [
|
|
412
|
+
fs.readFileSync('./root-ca.pem'),
|
|
413
|
+
fs.readFileSync('./intermediate-ca.pem'),
|
|
414
|
+
],
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Error: "certificate has expired"
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
Error: certificate has expired
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Solution:** Renew your certificates. For development, generate new self-signed certificates.
|
|
425
|
+
|
|
426
|
+
### Error: "ENOENT: no such file or directory"
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
Error: ENOENT: no such file or directory, open './certs/client-cert.pem'
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Solution:** Check that certificate file paths are correct:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import * as path from 'path';
|
|
436
|
+
|
|
437
|
+
// Use absolute path
|
|
438
|
+
const certPath = path.join(__dirname, 'certs', 'client-cert.pem');
|
|
439
|
+
console.log('Certificate path:', certPath);
|
|
440
|
+
const cert = fs.readFileSync(certPath);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Error: "sslmode value 'require' invalid"
|
|
444
|
+
|
|
445
|
+
If using a connection string, SSL configuration must be in the config object:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// ✗ Wrong - sslmode in connection string doesn't configure node-postgres properly
|
|
449
|
+
const client = new Client({
|
|
450
|
+
connectionString: 'postgresql://user:pass@host/db?sslmode=require'
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ✓ Correct - use ssl config object
|
|
454
|
+
const client = new Client({
|
|
455
|
+
connectionString: 'postgresql://user:pass@host/db',
|
|
456
|
+
ssl: {
|
|
457
|
+
rejectUnauthorized: true,
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Testing SSL Connection
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
import { Client } from 'que-ts';
|
|
466
|
+
|
|
467
|
+
async function testSSLConnection() {
|
|
468
|
+
const client = new Client({
|
|
469
|
+
host: 'your-host.com',
|
|
470
|
+
port: 5432,
|
|
471
|
+
database: 'your_database',
|
|
472
|
+
user: 'your_user',
|
|
473
|
+
password: 'your_password',
|
|
474
|
+
ssl: {
|
|
475
|
+
rejectUnauthorized: true,
|
|
476
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
477
|
+
key: fs.readFileSync('./client-key.pem'),
|
|
478
|
+
ca: fs.readFileSync('./ca-cert.pem'),
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const job = await client.enqueue('TestJob', []);
|
|
484
|
+
console.log('✓ SSL connection successful! Job ID:', job.id);
|
|
485
|
+
await client.close();
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error('✗ SSL connection failed:', error.message);
|
|
488
|
+
if (error.code) {
|
|
489
|
+
console.error('Error code:', error.code);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
testSSLConnection();
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Security Best Practices
|
|
498
|
+
|
|
499
|
+
1. **Always use `rejectUnauthorized: true` in production**
|
|
500
|
+
2. **Never commit certificates or private keys to version control**
|
|
501
|
+
3. **Use environment variables for sensitive data**
|
|
502
|
+
4. **Rotate certificates regularly**
|
|
503
|
+
5. **Use strong passphrases for encrypted keys**
|
|
504
|
+
6. **Restrict file permissions on certificate files:**
|
|
505
|
+
```bash
|
|
506
|
+
chmod 600 client-key.pem
|
|
507
|
+
chmod 644 client-cert.pem
|
|
508
|
+
chmod 644 ca-cert.pem
|
|
509
|
+
```
|
|
510
|
+
7. **Store certificates securely** (e.g., AWS Secrets Manager, HashiCorp Vault)
|
|
511
|
+
|
|
512
|
+
## Additional Resources
|
|
513
|
+
|
|
514
|
+
- [PostgreSQL SSL Documentation](https://www.postgresql.org/docs/current/ssl-tcp.html)
|
|
515
|
+
- [Node.js TLS Documentation](https://nodejs.org/api/tls.html)
|
|
516
|
+
- [node-postgres SSL Guide](https://node-postgres.com/features/ssl)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { Job, EnqueueOptions, ClientConfig, JSONArray } from "./types";
|
|
3
|
+
export declare class Client {
|
|
4
|
+
private pool;
|
|
5
|
+
constructor(config?: ClientConfig);
|
|
6
|
+
enqueue(jobClass: string, args?: JSONArray, options?: EnqueueOptions): Promise<Job>;
|
|
7
|
+
enqueueInTx(client: PoolClient, jobClass: string, args?: JSONArray, options?: EnqueueOptions): Promise<Job>;
|
|
8
|
+
lockJob(queue?: string): Promise<Job | null>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,IAAI,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,YAAY,EAAU,SAAS,EAAE,MAAM,SAAS,CAAC;AAK/E,qBAAa,MAAM;IACjB,OAAO,CAAC,IAAI,CAAO;gBAEP,MAAM,GAAE,YAAiB;IAe/B,OAAO,CACX,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,SAAc,EACpB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,GAAG,CAAC;IAiBT,WAAW,CACf,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,SAAc,EACpB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,GAAG,CAAC;IAiBT,OAAO,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAWhD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Client = void 0;
|
|
4
|
+
const pg_1 = require("pg");
|
|
5
|
+
const job_1 = require("./job");
|
|
6
|
+
const sql_1 = require("./sql");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
class Client {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.pool = new pg_1.Pool({
|
|
11
|
+
connectionString: config.connectionString,
|
|
12
|
+
host: config.host,
|
|
13
|
+
port: config.port,
|
|
14
|
+
database: config.database,
|
|
15
|
+
user: config.user,
|
|
16
|
+
password: config.password,
|
|
17
|
+
ssl: config.ssl,
|
|
18
|
+
max: config.maxConnections || 10,
|
|
19
|
+
idleTimeoutMillis: 5000, // Close idle connections after 5 seconds
|
|
20
|
+
connectionTimeoutMillis: 5000, // Timeout connection attempts after 5 seconds
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async enqueue(jobClass, args = [], options = {}) {
|
|
24
|
+
const { priority = 100, runAt = new Date(), queue = "" } = options;
|
|
25
|
+
const argsJson = (0, utils_1.formatJobArgs)(args);
|
|
26
|
+
const result = await this.pool.query(sql_1.SQL_QUERIES.ENQUEUE_JOB, [
|
|
27
|
+
jobClass,
|
|
28
|
+
argsJson,
|
|
29
|
+
priority,
|
|
30
|
+
runAt,
|
|
31
|
+
queue,
|
|
32
|
+
]);
|
|
33
|
+
const row = result.rows[0];
|
|
34
|
+
return new job_1.JobInstance(row, this.pool);
|
|
35
|
+
}
|
|
36
|
+
async enqueueInTx(client, jobClass, args = [], options = {}) {
|
|
37
|
+
const { priority = 100, runAt = new Date(), queue = "" } = options;
|
|
38
|
+
const argsJson = (0, utils_1.formatJobArgs)(args);
|
|
39
|
+
const result = await client.query(sql_1.SQL_QUERIES.ENQUEUE_JOB, [
|
|
40
|
+
jobClass,
|
|
41
|
+
argsJson,
|
|
42
|
+
priority,
|
|
43
|
+
runAt,
|
|
44
|
+
queue,
|
|
45
|
+
]);
|
|
46
|
+
const row = result.rows[0];
|
|
47
|
+
return new job_1.JobInstance(row, this.pool);
|
|
48
|
+
}
|
|
49
|
+
async lockJob(queue = "") {
|
|
50
|
+
const result = await this.pool.query(sql_1.SQL_QUERIES.LOCK_JOB, [queue]);
|
|
51
|
+
if (result.rows.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const row = result.rows[0];
|
|
55
|
+
return new job_1.JobInstance(row, this.pool);
|
|
56
|
+
}
|
|
57
|
+
async close() {
|
|
58
|
+
await this.pool.end();
|
|
59
|
+
// Small delay to ensure all connections are fully closed
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.Client = Client;
|
|
64
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { DashboardService, DashboardOptions } from './service';
|
|
4
|
+
export interface DashboardMiddlewareOptions extends DashboardOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Optional authentication middleware
|
|
7
|
+
* Return true to allow access, false to deny
|
|
8
|
+
*/
|
|
9
|
+
auth?: (req: Request, res: Response, next: NextFunction) => boolean | Promise<boolean>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Creates an Express router with the Que dashboard
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import express from 'express';
|
|
17
|
+
* import { Pool } from 'pg';
|
|
18
|
+
* import { createDashboard } from 'que-ts/dashboard';
|
|
19
|
+
*
|
|
20
|
+
* const app = express();
|
|
21
|
+
* const pool = new Pool({ ... });
|
|
22
|
+
*
|
|
23
|
+
* app.use('/admin/queue', createDashboard(pool, {
|
|
24
|
+
* title: 'My App Queue',
|
|
25
|
+
* auth: (req, res, next) => {
|
|
26
|
+
* // Add your authentication logic
|
|
27
|
+
* return req.isAuthenticated();
|
|
28
|
+
* }
|
|
29
|
+
* }));
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createDashboard(pool: Pool, options?: DashboardMiddlewareOptions): Router;
|
|
33
|
+
export { DashboardService, DashboardOptions };
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG/D,MAAM,WAAW,0BAA2B,SAAQ,gBAAgB;IAClE;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,0BAA+B,GACvC,MAAM,CAqJR;AAED,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC"}
|