@pgpm/jwt-claims 0.4.0 → 0.5.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
package/README.md
CHANGED
|
@@ -2,4 +2,408 @@
|
|
|
2
2
|
|
|
3
3
|
JWT claim handling and validation functions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@pgpm/jwt-claims` provides PostgreSQL functions for extracting and working with JWT (JSON Web Token) claims stored in PostgreSQL session variables. This package enables seamless integration between JWT-based authentication systems and PostgreSQL, allowing database functions to access user context, group memberships, IP addresses, and other JWT payload data.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **User Context Functions**: Extract user ID from JWT claims
|
|
12
|
+
- **Group Membership**: Access user's group IDs
|
|
13
|
+
- **Request Metadata**: Get IP address and user agent from requests
|
|
14
|
+
- **Database Context**: Access database ID from JWT claims
|
|
15
|
+
- **Type-Safe Extraction**: Proper error handling for invalid claim values
|
|
16
|
+
- **Session Variables**: Uses PostgreSQL's `current_setting()` for claim storage
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
If you have `pgpm` installed:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pgpm install @pgpm/jwt-claims
|
|
24
|
+
pgpm deploy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This is a quick way to get started. The sections below provide more detailed installation options.
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install pgpm globally
|
|
33
|
+
npm install -g pgpm
|
|
34
|
+
|
|
35
|
+
# Start PostgreSQL
|
|
36
|
+
pgpm docker start
|
|
37
|
+
|
|
38
|
+
# Set environment variables
|
|
39
|
+
eval "$(pgpm env)"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Deploy
|
|
43
|
+
|
|
44
|
+
#### Option 1: Deploy by installing with pgpm
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pgpm install @pgpm/jwt-claims
|
|
48
|
+
pgpm deploy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Option 2: Deploy from Package Directory
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd packages/security/jwt-claims
|
|
55
|
+
pgpm deploy --createdb
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Option 3: Deploy from Workspace Root
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Install workspace dependencies
|
|
62
|
+
pgpm install
|
|
63
|
+
|
|
64
|
+
# Deploy with dependencies
|
|
65
|
+
pgpm deploy mydb1 --yes --createdb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Core Functions
|
|
69
|
+
|
|
70
|
+
### jwt_public.current_user_id()
|
|
71
|
+
Extracts the user ID from JWT claims.
|
|
72
|
+
|
|
73
|
+
**Returns:** `uuid` - The current user's ID, or NULL if not set
|
|
74
|
+
|
|
75
|
+
**Usage:**
|
|
76
|
+
```sql
|
|
77
|
+
SELECT jwt_public.current_user_id();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**JWT Claim:** `jwt.claims.user_id`
|
|
81
|
+
|
|
82
|
+
### jwt_public.current_group_ids()
|
|
83
|
+
Extracts the user's group IDs from JWT claims.
|
|
84
|
+
|
|
85
|
+
**Returns:** `uuid[]` - Array of group IDs, or empty array if not set
|
|
86
|
+
|
|
87
|
+
**Usage:**
|
|
88
|
+
```sql
|
|
89
|
+
SELECT jwt_public.current_group_ids();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**JWT Claim:** `jwt.claims.group_ids`
|
|
93
|
+
|
|
94
|
+
### jwt_public.current_ip_address()
|
|
95
|
+
Extracts the client's IP address from JWT claims.
|
|
96
|
+
|
|
97
|
+
**Returns:** `text` - The client's IP address, or NULL if not set
|
|
98
|
+
|
|
99
|
+
**Usage:**
|
|
100
|
+
```sql
|
|
101
|
+
SELECT jwt_public.current_ip_address();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**JWT Claim:** `jwt.claims.ip_address`
|
|
105
|
+
|
|
106
|
+
### jwt_public.current_user_agent()
|
|
107
|
+
Extracts the client's user agent from JWT claims.
|
|
108
|
+
|
|
109
|
+
**Returns:** `text` - The client's user agent string, or NULL if not set
|
|
110
|
+
|
|
111
|
+
**Usage:**
|
|
112
|
+
```sql
|
|
113
|
+
SELECT jwt_public.current_user_agent();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**JWT Claim:** `jwt.claims.user_agent`
|
|
117
|
+
|
|
118
|
+
### jwt_private.current_database_id()
|
|
119
|
+
Extracts the database ID from JWT claims (private function).
|
|
120
|
+
|
|
121
|
+
**Returns:** `uuid` - The database ID, or NULL if not set
|
|
122
|
+
|
|
123
|
+
**Usage:**
|
|
124
|
+
```sql
|
|
125
|
+
SELECT jwt_private.current_database_id();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**JWT Claim:** `jwt.claims.database_id`
|
|
129
|
+
|
|
130
|
+
## Usage
|
|
131
|
+
|
|
132
|
+
### Setting JWT Claims
|
|
133
|
+
|
|
134
|
+
JWT claims are set as PostgreSQL session variables, typically by your authentication middleware:
|
|
135
|
+
|
|
136
|
+
```sql
|
|
137
|
+
-- Set user ID claim
|
|
138
|
+
SELECT set_config('jwt.claims.user_id', 'user-uuid-here', false);
|
|
139
|
+
|
|
140
|
+
-- Set group IDs claim
|
|
141
|
+
SELECT set_config('jwt.claims.group_ids', '{uuid1,uuid2,uuid3}', false);
|
|
142
|
+
|
|
143
|
+
-- Set IP address claim
|
|
144
|
+
SELECT set_config('jwt.claims.ip_address', '192.168.1.1', false);
|
|
145
|
+
|
|
146
|
+
-- Set user agent claim
|
|
147
|
+
SELECT set_config('jwt.claims.user_agent', 'Mozilla/5.0...', false);
|
|
148
|
+
|
|
149
|
+
-- Set database ID claim
|
|
150
|
+
SELECT set_config('jwt.claims.database_id', 'database-uuid-here', false);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Using Claims in Row-Level Security
|
|
154
|
+
|
|
155
|
+
```sql
|
|
156
|
+
-- Enable RLS on a table
|
|
157
|
+
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
|
158
|
+
|
|
159
|
+
-- Users can only see their own posts
|
|
160
|
+
CREATE POLICY user_posts ON posts
|
|
161
|
+
FOR ALL
|
|
162
|
+
TO authenticated
|
|
163
|
+
USING (user_id = jwt_public.current_user_id());
|
|
164
|
+
|
|
165
|
+
-- Users can see posts from their groups
|
|
166
|
+
CREATE POLICY group_posts ON posts
|
|
167
|
+
FOR SELECT
|
|
168
|
+
TO authenticated
|
|
169
|
+
USING (group_id = ANY(jwt_public.current_group_ids()));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Using Claims in Functions
|
|
173
|
+
|
|
174
|
+
```sql
|
|
175
|
+
-- Function that uses current user ID
|
|
176
|
+
CREATE FUNCTION create_post(title text, content text)
|
|
177
|
+
RETURNS uuid AS $$
|
|
178
|
+
DECLARE
|
|
179
|
+
new_post_id uuid;
|
|
180
|
+
BEGIN
|
|
181
|
+
INSERT INTO posts (user_id, title, content)
|
|
182
|
+
VALUES (jwt_public.current_user_id(), title, content)
|
|
183
|
+
RETURNING id INTO new_post_id;
|
|
184
|
+
|
|
185
|
+
RETURN new_post_id;
|
|
186
|
+
END;
|
|
187
|
+
$$ LANGUAGE plpgsql;
|
|
188
|
+
|
|
189
|
+
-- Function that checks group membership
|
|
190
|
+
CREATE FUNCTION user_in_group(group_id uuid)
|
|
191
|
+
RETURNS boolean AS $$
|
|
192
|
+
BEGIN
|
|
193
|
+
RETURN group_id = ANY(jwt_public.current_group_ids());
|
|
194
|
+
END;
|
|
195
|
+
$$ LANGUAGE plpgsql;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Audit Logging with JWT Claims
|
|
199
|
+
|
|
200
|
+
```sql
|
|
201
|
+
-- Audit log table
|
|
202
|
+
CREATE TABLE audit_log (
|
|
203
|
+
id serial PRIMARY KEY,
|
|
204
|
+
user_id uuid,
|
|
205
|
+
ip_address text,
|
|
206
|
+
user_agent text,
|
|
207
|
+
action text,
|
|
208
|
+
timestamp timestamptz DEFAULT now()
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
-- Trigger function for audit logging
|
|
212
|
+
CREATE FUNCTION log_action()
|
|
213
|
+
RETURNS trigger AS $$
|
|
214
|
+
BEGIN
|
|
215
|
+
INSERT INTO audit_log (user_id, ip_address, user_agent, action)
|
|
216
|
+
VALUES (
|
|
217
|
+
jwt_public.current_user_id(),
|
|
218
|
+
jwt_public.current_ip_address(),
|
|
219
|
+
jwt_public.current_user_agent(),
|
|
220
|
+
TG_OP || ' on ' || TG_TABLE_NAME
|
|
221
|
+
);
|
|
222
|
+
RETURN NEW;
|
|
223
|
+
END;
|
|
224
|
+
$$ LANGUAGE plpgsql;
|
|
225
|
+
|
|
226
|
+
-- Add trigger to table
|
|
227
|
+
CREATE TRIGGER audit_posts
|
|
228
|
+
AFTER INSERT OR UPDATE OR DELETE ON posts
|
|
229
|
+
FOR EACH ROW
|
|
230
|
+
EXECUTE FUNCTION log_action();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Multi-Tenancy with Database ID
|
|
234
|
+
|
|
235
|
+
```sql
|
|
236
|
+
-- Filter data by database ID
|
|
237
|
+
CREATE FUNCTION get_tenant_data()
|
|
238
|
+
RETURNS SETOF my_table AS $$
|
|
239
|
+
BEGIN
|
|
240
|
+
RETURN QUERY
|
|
241
|
+
SELECT * FROM my_table
|
|
242
|
+
WHERE database_id = jwt_private.current_database_id();
|
|
243
|
+
END;
|
|
244
|
+
$$ LANGUAGE plpgsql;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Integration with Other Packages
|
|
248
|
+
|
|
249
|
+
### With @pgpm/stamps
|
|
250
|
+
|
|
251
|
+
The stamps package uses JWT claims for automatic user tracking:
|
|
252
|
+
|
|
253
|
+
```sql
|
|
254
|
+
-- Stamps automatically uses jwt_public.current_user_id()
|
|
255
|
+
-- for created_by and updated_by columns
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### With @pgpm/achievements
|
|
259
|
+
|
|
260
|
+
The achievements package uses JWT claims for user context:
|
|
261
|
+
|
|
262
|
+
```sql
|
|
263
|
+
-- Check current user's achievements
|
|
264
|
+
SELECT * FROM status_public.steps_required('newbie');
|
|
265
|
+
-- Uses jwt_public.current_user_id() internally
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### With @pgpm/default-roles
|
|
269
|
+
|
|
270
|
+
Combine JWT claims with role-based access:
|
|
271
|
+
|
|
272
|
+
```sql
|
|
273
|
+
-- Set role based on JWT claim
|
|
274
|
+
CREATE FUNCTION set_user_role()
|
|
275
|
+
RETURNS void AS $$
|
|
276
|
+
DECLARE
|
|
277
|
+
user_role text;
|
|
278
|
+
BEGIN
|
|
279
|
+
user_role := current_setting('jwt.claims.role', true);
|
|
280
|
+
|
|
281
|
+
IF user_role = 'admin' THEN
|
|
282
|
+
SET LOCAL ROLE administrator;
|
|
283
|
+
ELSIF user_role = 'user' THEN
|
|
284
|
+
SET LOCAL ROLE authenticated;
|
|
285
|
+
ELSE
|
|
286
|
+
SET LOCAL ROLE anonymous;
|
|
287
|
+
END IF;
|
|
288
|
+
END;
|
|
289
|
+
$$ LANGUAGE plpgsql;
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Error Handling
|
|
293
|
+
|
|
294
|
+
All functions include error handling for invalid claim values:
|
|
295
|
+
|
|
296
|
+
```sql
|
|
297
|
+
-- If jwt.claims.user_id is not a valid UUID
|
|
298
|
+
SELECT jwt_public.current_user_id();
|
|
299
|
+
-- Returns NULL and raises NOTICE: 'Invalid UUID value'
|
|
300
|
+
|
|
301
|
+
-- If jwt.claims.group_ids is not a valid UUID array
|
|
302
|
+
SELECT jwt_public.current_group_ids();
|
|
303
|
+
-- Returns empty array [] and raises NOTICE: 'Invalid UUID value'
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Security Considerations
|
|
307
|
+
|
|
308
|
+
1. **Trust the Source**: Only set JWT claims from trusted authentication middleware
|
|
309
|
+
2. **Validate Claims**: Always validate JWT signatures before setting claims
|
|
310
|
+
3. **Session Scope**: Claims are session-scoped and don't persist across connections
|
|
311
|
+
4. **No Direct Access**: Users cannot directly modify session variables in most configurations
|
|
312
|
+
5. **Use HTTPS**: Always transmit JWTs over HTTPS to prevent interception
|
|
313
|
+
|
|
314
|
+
## Dependencies
|
|
315
|
+
|
|
316
|
+
- `@pgpm/types`: Core PostgreSQL types
|
|
317
|
+
- `@pgpm/verify`: Verification utilities
|
|
318
|
+
|
|
319
|
+
## Testing
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
pnpm test
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Development
|
|
326
|
+
|
|
327
|
+
See the [Development](#development) section below for information on working with this package.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Development
|
|
332
|
+
|
|
333
|
+
### **Before You Begin**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# 1. Install pgpm
|
|
337
|
+
npm install -g pgpm
|
|
338
|
+
|
|
339
|
+
# 2. Start Postgres (Docker or local)
|
|
340
|
+
pgpm docker start
|
|
341
|
+
|
|
342
|
+
# 3. Load PG* environment variables (PGHOST, PGUSER, ...)
|
|
343
|
+
eval "$(pgpm env)"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### **Starting a New Project**
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# 1. Create a workspace
|
|
352
|
+
pgpm init --workspace
|
|
353
|
+
cd my-app
|
|
354
|
+
|
|
355
|
+
# 2. Create your first module
|
|
356
|
+
pgpm init
|
|
357
|
+
|
|
358
|
+
# 3. Add a migration
|
|
359
|
+
pgpm add some_change
|
|
360
|
+
|
|
361
|
+
# 4. Deploy (auto-creates database)
|
|
362
|
+
pgpm deploy --createdb
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### **Working With an Existing Project**
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# 1. Clone and enter the project
|
|
371
|
+
git clone <repo> && cd <project>
|
|
372
|
+
|
|
373
|
+
# 2. Install dependencies
|
|
374
|
+
pnpm install
|
|
375
|
+
|
|
376
|
+
# 3. Deploy locally
|
|
377
|
+
pgpm deploy --createdb
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### **Testing a Module Inside a Workspace**
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# 1. Install workspace deps
|
|
386
|
+
pnpm install
|
|
387
|
+
|
|
388
|
+
# 2. Enter the module directory
|
|
389
|
+
cd packages/<some-module>
|
|
390
|
+
|
|
391
|
+
# 3. Run tests in watch mode
|
|
392
|
+
pnpm test:watch
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Related Tooling
|
|
396
|
+
|
|
397
|
+
* [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.
|
|
398
|
+
* [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.
|
|
399
|
+
* [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.
|
|
400
|
+
* [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.
|
|
401
|
+
* [pgsql-parser](https://github.com/launchql/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
402
|
+
* [libpg-query-node](https://github.com/launchql/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
403
|
+
* [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.
|
|
404
|
+
|
|
405
|
+
## Disclaimer
|
|
406
|
+
|
|
407
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
408
|
+
|
|
409
|
+
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.
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpm/jwt-claims",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "JWT claim handling and validation functions",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"bundle": "
|
|
9
|
+
"bundle": "pgpm package",
|
|
10
10
|
"test": "jest",
|
|
11
11
|
"test:watch": "jest --watch"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"
|
|
14
|
+
"pgpm": "^0.2.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@pgpm/types": "0.
|
|
18
|
-
"@pgpm/verify": "0.
|
|
17
|
+
"@pgpm/types": "0.5.0",
|
|
18
|
+
"@pgpm/verify": "0.5.0"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"bugs": {
|
|
26
26
|
"url": "https://github.com/launchql/extensions/issues"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "d8eedbb24ad22a106634bc3b919bfb8d41976c16"
|
|
29
29
|
}
|
package/sqitch.plan
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
%syntax-version=1.0.0
|
|
2
|
-
%project=launchql-jwt-claims
|
|
3
|
-
%uri=launchql-jwt-claims
|
|
4
|
-
|
|
5
|
-
schemas/jwt_public/schema 2020-12-17T06:47:29Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_public/schema
|
|
6
|
-
schemas/jwt_private/schema 2020-12-17T06:47:34Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_private/schema
|
|
7
|
-
schemas/jwt_public/procedures/current_user_id [schemas/jwt_public/schema] 2020-12-17T06:48:56Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_public/procedures/current_user_id
|
|
8
|
-
schemas/jwt_public/procedures/current_ip_address [schemas/jwt_public/schema] 2020-12-17T23:19:17Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_public/procedures/current_ip_address
|
|
9
|
-
schemas/jwt_public/procedures/current_user_agent [schemas/jwt_public/schema] 2020-12-17T23:20:04Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_public/procedures/current_user_agent
|
|
10
|
-
schemas/jwt_private/procedures/current_database_id [schemas/jwt_private/schema] 2020-12-17T23:22:28Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_private/procedures/current_database_id
|
|
11
|
-
schemas/jwt_public/procedures/current_group_ids [schemas/jwt_public/schema] 2020-12-17T23:30:50Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/jwt_public/procedures/current_group_ids
|
|
12
|
-
|
|
13
|
-
[This file mirrors launchql.plan for harness compatibility during tests and local deploys. Do not hand-edit one without the other.]
|
|
File without changes
|
|
File without changes
|