@pgpm/encrypted-secrets 0.4.0 → 0.6.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/Makefile CHANGED
@@ -1,5 +1,5 @@
1
1
  EXTENSION = launchql-encrypted-secrets
2
- DATA = sql/launchql-encrypted-secrets--0.1.0.sql
2
+ DATA = sql/launchql-encrypted-secrets--0.5.0.sql
3
3
 
4
4
  PG_CONFIG = pg_config
5
5
  PGXS := $(shell $(PG_CONFIG) --pgxs)
package/README.md CHANGED
@@ -2,4 +2,563 @@
2
2
 
3
3
  Encrypted secrets management for PostgreSQL.
4
4
 
5
- Provides secure storage and retrieval of encrypted secrets, API keys, and sensitive configuration data within PostgreSQL.
5
+ ## Overview
6
+
7
+ `@pgpm/encrypted-secrets` provides a comprehensive API layer for managing encrypted secrets in PostgreSQL. Built on top of `@pgpm/encrypted-secrets-table`, this package offers high-level functions for storing, retrieving, verifying, and deleting encrypted secrets with support for multiple encryption methods (PGP and crypt). It includes role-based access control and integrates seamlessly with PostgreSQL's encryption extensions.
8
+
9
+ ## Features
10
+
11
+ - **Multiple Encryption Methods**: Support for PGP symmetric encryption and crypt hashing
12
+ - **High-Level API**: Simple functions for secret management (upsert, get, verify, delete)
13
+ - **Role-Based Access**: Grants execute permissions to authenticated users
14
+ - **Automatic Encryption**: Secrets are encrypted before storage
15
+ - **Verification Support**: Verify secrets without exposing stored values
16
+ - **Batch Operations**: Delete multiple secrets at once
17
+ - **Default Values**: Return default values when secrets don't exist
18
+
19
+ ## Installation
20
+
21
+ If you have `pgpm` installed:
22
+
23
+ ```bash
24
+ pgpm install @pgpm/encrypted-secrets
25
+ pgpm deploy
26
+ ```
27
+
28
+ This is a quick way to get started. The sections below provide more detailed installation options.
29
+
30
+ ### Prerequisites
31
+
32
+ ```bash
33
+ # Install pgpm globally
34
+ npm install -g pgpm
35
+
36
+ # Start PostgreSQL
37
+ pgpm docker start
38
+
39
+ # Set environment variables
40
+ eval "$(pgpm env)"
41
+ ```
42
+
43
+ ### Deploy
44
+
45
+ #### Option 1: Deploy by installing with pgpm
46
+
47
+ ```bash
48
+ pgpm install @pgpm/encrypted-secrets
49
+ pgpm deploy
50
+ ```
51
+
52
+ #### Option 2: Deploy from Package Directory
53
+
54
+ ```bash
55
+ cd packages/security/encrypted-secrets
56
+ pgpm deploy --createdb
57
+ ```
58
+
59
+ #### Option 3: Deploy from Workspace Root
60
+
61
+ ```bash
62
+ # Install workspace dependencies
63
+ pnpm install
64
+
65
+ # Deploy with dependencies
66
+ pgpm deploy mydb1 --yes --createdb
67
+ ```
68
+
69
+ ## Core Functions
70
+
71
+ ### encrypted_secrets.secrets_upsert()
72
+
73
+ Insert or update a secret.
74
+
75
+ **Signature:**
76
+ ```sql
77
+ encrypted_secrets.secrets_upsert(
78
+ v_secrets_owned_field uuid,
79
+ secret_name text,
80
+ secret_value text,
81
+ field_encoding text DEFAULT 'pgp'
82
+ ) RETURNS boolean
83
+ ```
84
+
85
+ **Parameters:**
86
+ - `v_secrets_owned_field`: UUID of the entity that owns this secret
87
+ - `secret_name`: Name/identifier for the secret
88
+ - `secret_value`: The secret value to encrypt and store
89
+ - `field_encoding`: Encryption method ('pgp' or 'crypt')
90
+
91
+ **Returns:** `TRUE` on success
92
+
93
+ **Usage:**
94
+ ```sql
95
+ -- Store a secret with PGP encryption
96
+ SELECT encrypted_secrets.secrets_upsert(
97
+ 'user-uuid',
98
+ 'github_token',
99
+ 'ghp_xxxxxxxxxxxx',
100
+ 'pgp'
101
+ );
102
+
103
+ -- Store a password with crypt hashing
104
+ SELECT encrypted_secrets.secrets_upsert(
105
+ 'user-uuid',
106
+ 'password',
107
+ 'my-secure-password',
108
+ 'crypt'
109
+ );
110
+ ```
111
+
112
+ ### encrypted_secrets.secrets_getter()
113
+
114
+ Retrieve and decrypt a secret.
115
+
116
+ **Signature:**
117
+ ```sql
118
+ encrypted_secrets.secrets_getter(
119
+ secrets_owned_field uuid,
120
+ secret_name text,
121
+ default_value text DEFAULT NULL
122
+ ) RETURNS text
123
+ ```
124
+
125
+ **Parameters:**
126
+ - `secrets_owned_field`: UUID of the secret owner
127
+ - `secret_name`: Name of the secret to retrieve
128
+ - `default_value`: Value to return if secret doesn't exist
129
+
130
+ **Returns:** Decrypted secret value or default value
131
+
132
+ **Usage:**
133
+ ```sql
134
+ -- Get a secret
135
+ SELECT encrypted_secrets.secrets_getter(
136
+ 'user-uuid',
137
+ 'github_token'
138
+ );
139
+
140
+ -- Get a secret with default value
141
+ SELECT encrypted_secrets.secrets_getter(
142
+ 'user-uuid',
143
+ 'api_key',
144
+ 'default-key-value'
145
+ );
146
+ ```
147
+
148
+ ### encrypted_secrets.secrets_verify()
149
+
150
+ Verify a secret without retrieving it.
151
+
152
+ **Signature:**
153
+ ```sql
154
+ encrypted_secrets.secrets_verify(
155
+ secrets_owned_field uuid,
156
+ secret_name text,
157
+ secret_value text
158
+ ) RETURNS boolean
159
+ ```
160
+
161
+ **Parameters:**
162
+ - `secrets_owned_field`: UUID of the secret owner
163
+ - `secret_name`: Name of the secret to verify
164
+ - `secret_value`: Value to verify against stored secret
165
+
166
+ **Returns:** `TRUE` if values match, `FALSE` otherwise
167
+
168
+ **Usage:**
169
+ ```sql
170
+ -- Verify a password
171
+ SELECT encrypted_secrets.secrets_verify(
172
+ 'user-uuid',
173
+ 'password',
174
+ 'user-provided-password'
175
+ );
176
+
177
+ -- Use in authentication
178
+ DO $$
179
+ DECLARE
180
+ is_valid boolean;
181
+ BEGIN
182
+ SELECT encrypted_secrets.secrets_verify(
183
+ current_user_id(),
184
+ 'password',
185
+ 'user-input'
186
+ ) INTO is_valid;
187
+
188
+ IF NOT is_valid THEN
189
+ RAISE EXCEPTION 'Invalid password';
190
+ END IF;
191
+ END $$;
192
+ ```
193
+
194
+ ### encrypted_secrets.secrets_delete()
195
+
196
+ Delete one or more secrets.
197
+
198
+ **Signatures:**
199
+ ```sql
200
+ -- Delete single secret
201
+ encrypted_secrets.secrets_delete(
202
+ secrets_owned_field uuid,
203
+ secret_name text
204
+ ) RETURNS void
205
+
206
+ -- Delete multiple secrets
207
+ encrypted_secrets.secrets_delete(
208
+ secrets_owned_field uuid,
209
+ secret_names text[]
210
+ ) RETURNS void
211
+ ```
212
+
213
+ **Usage:**
214
+ ```sql
215
+ -- Delete a single secret
216
+ SELECT encrypted_secrets.secrets_delete(
217
+ 'user-uuid',
218
+ 'old_api_key'
219
+ );
220
+
221
+ -- Delete multiple secrets
222
+ SELECT encrypted_secrets.secrets_delete(
223
+ 'user-uuid',
224
+ ARRAY['temp_token', 'expired_key', 'old_password']
225
+ );
226
+ ```
227
+
228
+ ## Encryption Methods
229
+
230
+ ### PGP Encryption
231
+
232
+ PGP (Pretty Good Privacy) symmetric encryption provides strong encryption for secrets:
233
+
234
+ ```sql
235
+ -- Store with PGP
236
+ SELECT encrypted_secrets.secrets_upsert(
237
+ 'owner-uuid',
238
+ 'api_key',
239
+ 'secret-value',
240
+ 'pgp'
241
+ );
242
+
243
+ -- Retrieval automatically decrypts
244
+ SELECT encrypted_secrets.secrets_getter('owner-uuid', 'api_key');
245
+ ```
246
+
247
+ **Characteristics:**
248
+ - Reversible encryption (can decrypt)
249
+ - Uses owner UUID as encryption key
250
+ - Suitable for API keys, tokens, connection strings
251
+
252
+ ### Crypt Hashing
253
+
254
+ Crypt provides one-way hashing for passwords:
255
+
256
+ ```sql
257
+ -- Store with crypt
258
+ SELECT encrypted_secrets.secrets_upsert(
259
+ 'user-uuid',
260
+ 'password',
261
+ 'user-password',
262
+ 'crypt'
263
+ );
264
+
265
+ -- Verify password
266
+ SELECT encrypted_secrets.secrets_verify(
267
+ 'user-uuid',
268
+ 'password',
269
+ 'user-provided-password'
270
+ );
271
+ ```
272
+
273
+ **Characteristics:**
274
+ - One-way hashing (cannot decrypt)
275
+ - Suitable for passwords
276
+ - Use `secrets_verify()` to check passwords
277
+
278
+ ## Usage Patterns
279
+
280
+ ### User Authentication
281
+
282
+ ```sql
283
+ -- Register user with password
284
+ SELECT encrypted_secrets.secrets_upsert(
285
+ user_id,
286
+ 'password',
287
+ 'user-chosen-password',
288
+ 'crypt'
289
+ );
290
+
291
+ -- Login verification
292
+ CREATE FUNCTION authenticate_user(
293
+ p_user_id uuid,
294
+ p_password text
295
+ ) RETURNS boolean AS $$
296
+ BEGIN
297
+ RETURN encrypted_secrets.secrets_verify(
298
+ p_user_id,
299
+ 'password',
300
+ p_password
301
+ );
302
+ END;
303
+ $$ LANGUAGE plpgsql;
304
+ ```
305
+
306
+ ### API Key Management
307
+
308
+ ```sql
309
+ -- Store API keys for a service
310
+ SELECT encrypted_secrets.secrets_upsert(
311
+ organization_id,
312
+ 'stripe_secret_key',
313
+ 'sk_live_xxxxxxxxxxxx',
314
+ 'pgp'
315
+ );
316
+
317
+ SELECT encrypted_secrets.secrets_upsert(
318
+ organization_id,
319
+ 'stripe_publishable_key',
320
+ 'pk_live_xxxxxxxxxxxx',
321
+ 'pgp'
322
+ );
323
+
324
+ -- Retrieve for use
325
+ CREATE FUNCTION get_stripe_config(org_id uuid)
326
+ RETURNS jsonb AS $$
327
+ BEGIN
328
+ RETURN jsonb_build_object(
329
+ 'secret_key', encrypted_secrets.secrets_getter(org_id, 'stripe_secret_key'),
330
+ 'publishable_key', encrypted_secrets.secrets_getter(org_id, 'stripe_publishable_key')
331
+ );
332
+ END;
333
+ $$ LANGUAGE plpgsql;
334
+ ```
335
+
336
+ ### Database Credentials
337
+
338
+ ```sql
339
+ -- Store database connection strings
340
+ SELECT encrypted_secrets.secrets_upsert(
341
+ app_id,
342
+ 'db_connection_string',
343
+ 'postgresql://user:pass@host:5432/db',
344
+ 'pgp'
345
+ );
346
+
347
+ -- Retrieve for connection
348
+ SELECT encrypted_secrets.secrets_getter(
349
+ app_id,
350
+ 'db_connection_string'
351
+ );
352
+ ```
353
+
354
+ ### Secret Rotation
355
+
356
+ ```sql
357
+ -- Rotate an API key
358
+ DO $$
359
+ DECLARE
360
+ old_key text;
361
+ BEGIN
362
+ -- Get old key for migration
363
+ old_key := encrypted_secrets.secrets_getter('org-uuid', 'api_key');
364
+
365
+ -- Store new key
366
+ PERFORM encrypted_secrets.secrets_upsert(
367
+ 'org-uuid',
368
+ 'api_key',
369
+ 'new-api-key-value',
370
+ 'pgp'
371
+ );
372
+
373
+ -- Optionally keep old key temporarily
374
+ PERFORM encrypted_secrets.secrets_upsert(
375
+ 'org-uuid',
376
+ 'api_key_old',
377
+ old_key,
378
+ 'pgp'
379
+ );
380
+ END $$;
381
+ ```
382
+
383
+ ## Security Considerations
384
+
385
+ 1. **Encryption Keys**: The PGP encryption uses the owner UUID as the encryption key. Ensure UUIDs are not exposed.
386
+
387
+ 2. **Access Control**: Functions are granted to `authenticated` role. Use RLS on the underlying table for additional security.
388
+
389
+ 3. **Audit Logging**: Consider logging secret access:
390
+ ```sql
391
+ CREATE TABLE secret_access_log (
392
+ id serial PRIMARY KEY,
393
+ owner_id uuid,
394
+ secret_name text,
395
+ accessed_at timestamptz DEFAULT now(),
396
+ accessed_by uuid
397
+ );
398
+
399
+ -- Wrap getter with logging
400
+ CREATE FUNCTION get_secret_with_audit(
401
+ owner_id uuid,
402
+ secret_name text
403
+ ) RETURNS text AS $$
404
+ DECLARE
405
+ secret_value text;
406
+ BEGIN
407
+ secret_value := encrypted_secrets.secrets_getter(owner_id, secret_name);
408
+
409
+ INSERT INTO secret_access_log (owner_id, secret_name, accessed_by)
410
+ VALUES (owner_id, secret_name, current_user_id());
411
+
412
+ RETURN secret_value;
413
+ END;
414
+ $$ LANGUAGE plpgsql;
415
+ ```
416
+
417
+ 4. **Never Log Secrets**: Ensure application logs don't capture decrypted values.
418
+
419
+ 5. **Use HTTPS**: Always transmit secrets over encrypted connections.
420
+
421
+ ## Integration with Other Packages
422
+
423
+ ### With @pgpm/encrypted-secrets-table
424
+
425
+ This package builds directly on the storage layer:
426
+
427
+ ```sql
428
+ -- High-level API (this package)
429
+ SELECT encrypted_secrets.secrets_upsert('uuid', 'key', 'value');
430
+
431
+ -- Low-level storage (encrypted-secrets-table)
432
+ SELECT * FROM secrets_schema.secrets_table WHERE secrets_owned_field = 'uuid';
433
+ ```
434
+
435
+ ### With @pgpm/default-roles
436
+
437
+ Combine with role-based access control:
438
+
439
+ ```sql
440
+ -- Only authenticated users can manage secrets
441
+ GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_upsert TO authenticated;
442
+ GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_getter TO authenticated;
443
+ GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_verify TO authenticated;
444
+ GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_delete TO authenticated;
445
+ ```
446
+
447
+ ### With @pgpm/jwt-claims
448
+
449
+ Use JWT claims for owner context:
450
+
451
+ ```sql
452
+ -- Store secret for current user
453
+ SELECT encrypted_secrets.secrets_upsert(
454
+ jwt_public.current_user_id(),
455
+ 'personal_api_key',
456
+ 'key-value',
457
+ 'pgp'
458
+ );
459
+
460
+ -- Get secret for current user
461
+ SELECT encrypted_secrets.secrets_getter(
462
+ jwt_public.current_user_id(),
463
+ 'personal_api_key'
464
+ );
465
+ ```
466
+
467
+ ## Dependencies
468
+
469
+ - `@pgpm/default-roles`: Role-based access control
470
+ - `@pgpm/encrypted-secrets-table`: Storage layer
471
+ - `@pgpm/verify`: Verification utilities
472
+ - PostgreSQL pgcrypto extension (for encryption functions)
473
+
474
+ ## Testing
475
+
476
+ ```bash
477
+ pnpm test
478
+ ```
479
+
480
+ ## Development
481
+
482
+ See the [Development](#development) section below for information on working with this package.
483
+
484
+ ---
485
+
486
+ ## Development
487
+
488
+ ### **Before You Begin**
489
+
490
+ ```bash
491
+ # 1. Install pgpm
492
+ npm install -g pgpm
493
+
494
+ # 2. Start Postgres (Docker or local)
495
+ pgpm docker start
496
+
497
+ # 3. Load PG* environment variables (PGHOST, PGUSER, ...)
498
+ eval "$(pgpm env)"
499
+ ```
500
+
501
+ ---
502
+
503
+ ### **Starting a New Project**
504
+
505
+ ```bash
506
+ # 1. Create a workspace
507
+ pgpm init --workspace
508
+ cd my-app
509
+
510
+ # 2. Create your first module
511
+ pgpm init
512
+
513
+ # 3. Add a migration
514
+ pgpm add some_change
515
+
516
+ # 4. Deploy (auto-creates database)
517
+ pgpm deploy --createdb
518
+ ```
519
+
520
+ ---
521
+
522
+ ### **Working With an Existing Project**
523
+
524
+ ```bash
525
+ # 1. Clone and enter the project
526
+ git clone <repo> && cd <project>
527
+
528
+ # 2. Install dependencies
529
+ pnpm install
530
+
531
+ # 3. Deploy locally
532
+ pgpm deploy --createdb
533
+ ```
534
+
535
+ ---
536
+
537
+ ### **Testing a Module Inside a Workspace**
538
+
539
+ ```bash
540
+ # 1. Install workspace deps
541
+ pnpm install
542
+
543
+ # 2. Enter the module directory
544
+ cd packages/<some-module>
545
+
546
+ # 3. Run tests in watch mode
547
+ pnpm test:watch
548
+ ```
549
+
550
+ ## Related Tooling
551
+
552
+ * [pgpm](https://github.com/launchql/launchql/tree/main/packages/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
553
+ * [pgsql-test](https://github.com/launchql/launchql/tree/main/packages/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
554
+ * [supabase-test](https://github.com/launchql/launchql/tree/main/packages/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
555
+ * [graphile-test](https://github.com/launchql/launchql/tree/main/packages/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
556
+ * [pgsql-parser](https://github.com/launchql/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
557
+ * [libpg-query-node](https://github.com/launchql/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
558
+ * [pg-proto-parser](https://github.com/launchql/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
559
+
560
+ ## Disclaimer
561
+
562
+ AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
563
+
564
+ No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
@@ -1,6 +1,6 @@
1
1
  # launchql-encrypted-secrets extension
2
2
  comment = 'launchql-encrypted-secrets extension'
3
- default_version = '0.1.0'
3
+ default_version = '0.5.0'
4
4
  module_pathname = '$libdir/launchql-encrypted-secrets'
5
5
  requires = 'pgcrypto,plpgsql,uuid-ossp,launchql-encrypted-secrets-table,launchql-default-roles,launchql-verify'
6
6
  relocatable = false
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@pgpm/encrypted-secrets",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Encrypted secrets management for PostgreSQL",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "scripts": {
9
- "bundle": "lql package",
9
+ "bundle": "pgpm package",
10
10
  "test": "jest",
11
11
  "test:watch": "jest --watch"
12
12
  },
13
13
  "dependencies": {
14
- "@pgpm/default-roles": "0.4.0",
15
- "@pgpm/encrypted-secrets-table": "0.4.0",
16
- "@pgpm/verify": "0.4.0"
14
+ "@pgpm/default-roles": "0.6.0",
15
+ "@pgpm/encrypted-secrets-table": "0.6.0",
16
+ "@pgpm/verify": "0.6.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@launchql/cli": "^4.9.0"
19
+ "pgpm": "^0.2.0"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "https://github.com/launchql/extensions"
23
+ "url": "https://github.com/launchql/pgpm-modules"
24
24
  },
25
- "homepage": "https://github.com/launchql/extensions",
25
+ "homepage": "https://github.com/launchql/pgpm-modules",
26
26
  "bugs": {
27
- "url": "https://github.com/launchql/extensions/issues"
27
+ "url": "https://github.com/launchql/pgpm-modules/issues"
28
28
  },
29
- "gitHead": "cc9f52a335caa6e21ee7751b04b77c84ce6cb809"
29
+ "gitHead": "c7d0eae588d7a764b382a330c8b853b341b13fb2"
30
30
  }
File without changes