Skip to content

Commit dae4fc6

Browse files
committed
progress on extension
1 parent c02bbd3 commit dae4fc6

File tree

6 files changed

+170
-75
lines changed

6 files changed

+170
-75
lines changed

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
EXTENSION = pgjwt
2+
DATA = pgjwt--0.0.1.sql
3+
REGRESS = jwt_test # our test script file (without extension)
4+
5+
# postgres build stuff
6+
PG_CONFIG = pg_config
7+
PGXS := $(shell $(PG_CONFIG) --pgxs)
8+
include $(PGXS)

README.md

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
# pgjwt
22
PostgreSQL implementation of [JSON Web Tokens](https://jwt.io/)
33

4-
Dependencies
5-
------------
4+
## Dependencies
65

76
This code requires the pgcrypto extension, included in most
87
distribution's "postgresql-contrib" package. The tests require the
98
pgtap extension to run.
109

11-
Install
12-
-------
10+
## Install
1311

14-
'psql -f jwt.sql'
12+
Clone the repository and then run:
1513

16-
Note that this file will DROP and CREATE a new schema called 'jwt'.
17-
To run the tests install pgtap and run 'pg_prove test.sql'.
14+
'make install'
1815

16+
You will require sudo privledges in most cases. This creates a new
17+
extension that can be installed with 'CREATE EXTENSION pgjwt;' To run
18+
the tests install pgtap and run 'pg_prove test.sql'.
1919

20-
Usage
21-
-----
20+
21+
## Usage
2222

2323
Create a token. The first argument must be valid json, the second argument any text:
2424

25-
=> select jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret');
25+
=> select sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret');
2626
sign
2727
-------------------------------------------------------------------------------------------------------------------------------------------------------
2828
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
2929

3030
Verify a token:
3131

32-
=> select * from jwt.verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ', 'secret');
32+
=> select * from verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ', 'secret');
3333
header | payload | valid
3434
-----------------------------+-----------------------------------------------------+-------
3535
{"alg":"HS256","typ":"JWT"} | {"sub":"1234567890","name":"John Doe","admin":true} | t
@@ -40,6 +40,12 @@ Algorithm
4040
sign() and verify() take an optional algorithm argument that can be
4141
'HS256', 'HS384' or 'HS512'. The default is 'HS256':
4242

43-
=> select jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'HS384'),
43+
=> select sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'HS384'),
44+
45+
46+
47+
## TODO
4448

49+
* public/private keys
4550

51+
* SET ROLE and key lookup helper functions

jwt.sql

-63
This file was deleted.

pgjwt--0.0.1.sql

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
\echo Use "CREATE EXTENSION base36" to load this file. \quit
2+
3+
4+
CREATE OR REPLACE FUNCTION url_encode(data bytea) RETURNS text LANGUAGE sql AS $$
5+
SELECT translate(encode(data, 'base64'), E'+/=\n', '-_');
6+
$$;
7+
8+
9+
CREATE OR REPLACE FUNCTION url_decode(data text) RETURNS bytea LANGUAGE sql AS $$
10+
WITH t AS (SELECT translate(data, '-_', '+/')),
11+
rem AS (SELECT length((SELECT * FROM t)) % 4) -- compute padding size
12+
SELECT decode(
13+
(SELECT * FROM t) ||
14+
CASE WHEN (SELECT * FROM rem) > 0
15+
THEN repeat('=', (4 - (SELECT * FROM rem)))
16+
ELSE '' END,
17+
'base64');
18+
$$;
19+
20+
21+
CREATE OR REPLACE FUNCTION algorithm_sign(signables text, secret text, algorithm text)
22+
RETURNS text LANGUAGE sql AS $$
23+
WITH
24+
alg AS (
25+
SELECT CASE
26+
WHEN algorithm = 'HS256' THEN 'sha256'
27+
WHEN algorithm = 'HS384' THEN 'sha384'
28+
WHEN algorithm = 'HS512' THEN 'sha512'
29+
ELSE '' END) -- hmac throws error
30+
SELECT url_encode(hmac(signables, secret, (select * FROM alg)));
31+
$$;
32+
33+
34+
CREATE OR REPLACE FUNCTION sign(payload json, secret text, algorithm text DEFAULT 'HS256')
35+
RETURNS text LANGUAGE sql AS $$
36+
WITH
37+
header AS (
38+
SELECT url_encode(convert_to('{"alg":"' || algorithm || '","typ":"JWT"}', 'utf8'))
39+
),
40+
payload AS (
41+
SELECT url_encode(convert_to(payload::text, 'utf8'))
42+
),
43+
signables AS (
44+
SELECT (SELECT * FROM header) || '.' || (SELECT * FROM payload)
45+
)
46+
SELECT
47+
(SELECT * FROM signables)
48+
|| '.' ||
49+
algorithm_sign((SELECT * FROM signables), secret, algorithm);
50+
$$;
51+
52+
53+
CREATE OR REPLACE FUNCTION verify(token text, secret text, algorithm text DEFAULT 'HS256')
54+
RETURNS table(header json, payload json, valid boolean) LANGUAGE sql AS $$
55+
SELECT
56+
convert_from(url_decode(r[1]), 'utf8')::json AS header,
57+
convert_from(url_decode(r[2]), 'utf8')::json AS payload,
58+
r[3] = algorithm_sign(r[1] || '.' || r[2], secret, algorithm) AS valid
59+
FROM regexp_split_to_array(token, '\.') r;
60+
$$;
61+
COMMIT;

pgjwt.control

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# pgjwt extension
2+
comment = 'JSON Web Token API for Postgresql'
3+
default_version = '0.0.1'
4+
relocatable = true

sql/jwt_test.sql

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
SELECT jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret');
3+
4+
-- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');
5+
6+
SELECT jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'HS256');
7+
-- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');
8+
9+
SELECT jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'bogus');
10+
-- $$,
11+
-- '22023',
12+
-- 'Cannot use "": No such hash algorithm',
13+
-- 'sign() should raise on bogus algorithm'
14+
-- );
15+
16+
SELECT header::text, payload::text, valid FROM jwt.verify(
17+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ',
18+
'secret', 'bogus');
19+
-- '22023',
20+
-- 'Cannot use "": No such hash algorithm',
21+
-- 'verify() should raise on bogus algorithm'
22+
23+
SELECT header::text, payload::text, valid FROM jwt.verify(
24+
'eyJhbGciOiJIUzI1NiIBOGUScCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ',
25+
'secret', 'HS256');
26+
27+
SELECT header::text, payload::text, valid FROM jwt.verify(
28+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaBOGUS9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ',
29+
'secret', 'HS256');
30+
31+
32+
SELECT header::text, payload::text, valid FROM jwt.verify(
33+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ',
34+
'secret');
35+
-- $$VALUES ('{"alg":"HS256","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', true)$$,
36+
-- 'verify() should return return data marked valid'
37+
38+
39+
SELECT header::text, payload::text, valid FROM jwt.verify(
40+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ',
41+
'badsecret');
42+
43+
-- $$VALUES ('{"alg":"HS256","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', false)$$,
44+
-- 'verify() should return return data marked invalid'
45+
46+
47+
SELECT jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'HS384');
48+
-- E'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP-579IC2GJ7P3CtFw6nfTTPw-0lZUzqgWAo9QIQElyxOpoRm');
49+
50+
51+
SELECT header::text, payload::text, valid FROM jwt.verify(
52+
E'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP-579IC2GJ7P3CtFw6nfTTPw-0lZUzqgWAo9QIQElyxOpoRm',
53+
'secret', 'HS384');
54+
-- $$VALUES ('{"alg":"HS384","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', true)$$,
55+
-- 'verify() should return return data marked valid'
56+
57+
58+
SELECT header::text, payload::text, valid FROM jwt.verify(
59+
E'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.DtVnCyiYCsCbg8gUP-579IC2GJ7P3CtFw6nfTTPw-0lZUzqgWAo9QIQElyxOpoRm',
60+
'badsecret', 'HS384');
61+
-- $$VALUES ('{"alg":"HS384","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', false)$$,
62+
-- 'verify() should return return data marked invalid'
63+
64+
65+
SELECT jwt.sign('{"sub":"1234567890","name":"John Doe","admin":true}', 'secret', 'HS512');
66+
-- E'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.YI0rUGDq5XdRw8vW2sDLRNFMN8Waol03iSFH8I4iLzuYK7FKHaQYWzPt0BJFGrAmKJ6SjY0mJIMZqNQJFVpkuw');
67+
68+
69+
SELECT header::text, payload::text, valid FROM jwt.verify(
70+
E'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.YI0rUGDq5XdRw8vW2sDLRNFMN8Waol03iSFH8I4iLzuYK7FKHaQYWzPt0BJFGrAmKJ6SjY0mJIMZqNQJFVpkuw',
71+
'secret', 'HS512');
72+
-- $$VALUES ('{"alg":"HS512","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', true)$$,
73+
-- 'verify() should return return data marked valid'
74+
75+
SELECT header::text, payload::text, valid FROM jwt.verify(
76+
E'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.YI0rUGDq5XdRw8vW2sDLRNFMN8Waol03iSFH8I4iLzuYK7FKHaQYWzPt0BJFGrAmKJ6SjY0mJIMZqNQJFVpkuw',
77+
'badsecret', 'HS512');
78+
-- $$VALUES ('{"alg":"HS512","typ":"JWT"}', '{"sub":"1234567890","name":"John Doe","admin":true}', false)$$,
79+
-- 'verify() should return return data marked invalid'

0 commit comments

Comments
 (0)