1+ import { Hono } from 'hono' ;
2+ import { generateKeyPair , JSONWebKeySet , JWK , calculateJwkThumbprintUri } from 'jose' ;
3+ import { GenerateKeyPairResult } from 'jose/key/generate/keypair' ;
4+ import { exportJWK } from "jose/key/export" ;
5+ import { SignJWT } from "jose/jwt/sign" ;
6+ import { cors } from 'hono/cors' ;
7+
8+ const path = Deno . env . get ( 'PGRST_JWT_SECRET' ) ?. replace ( '@' , '' ) ?? '/tmp/jwks.json' ;
9+ const claims = JSON . parse ( Deno . env . get ( 'PGRST_JWT_CLAIMS' ) ?? '{}' ) ;
10+ const origin = Deno . env . get ( 'PGRST_CLIENT_ORIGIN' ) ?. split ( ',' ) ?? [ ] ;
11+ const trusted = Deno . env . get ( 'PGRST_JWK_TRUST' ) ?. split ( ',' ) ?? [ ] ;
12+ const cert = Deno . env . get ( 'PGRST_JWK_CERT' ) ?? 'cert.pem' ;
13+ const alg = Deno . env . get ( 'PGRST_JWT_ALG' ) ?? 'RS256' ;
14+ const iss = Deno . env . get ( 'PGRST_JWT_ISS' ) ?? 'http://localhost:8000/jwks'
15+ const aud = Deno . env . get ( 'PGRST_JWT_AUD' ) ?? 'postgrest' ;
16+ const exp = Deno . env . get ( 'PGRST_JWT_EXP' ) ?? '5 minutes' ;
17+ const sub = Deno . env . get ( 'PGRST_JWT_SUB' ) ?? 'anon' ;
18+ const api = Deno . env . get ( 'PGRST_CLIENT_KEY' ) ?? '' ;
19+
20+ let keypair : GenerateKeyPairResult ;
21+ const initialize = async ( ) => {
22+ keypair = await generateKeyPair ( alg , { extractable : true } ) ;
23+ const keysets = await jwk ( keypair . publicKey , ...await upstream ( ) ) ;
24+
25+ keysets . keys [ 0 ] . kid = await calculateJwkThumbprintUri ( keysets . keys [ 0 ] )
26+
27+ await write ( keysets ) ;
28+ localStorage . setItem ( 'jwk:kid' , keysets . keys [ 0 ] . kid ) ;
29+ localStorage . setItem ( 'jwk:val' , JSON . stringify ( keysets . keys [ 0 ] ) )
30+ }
31+
32+ const upstream = async ( ) : Promise < Array < JWK > > => {
33+ const certificate = await Deno . readTextFile ( cert ) . catch ( ( error ) => {
34+ console . warn ( error )
35+ return undefined
36+ } )
37+ const caCerts = certificate ? [ certificate ] : [ ] ;
38+
39+ const client = Deno . createHttpClient ( { caCerts } )
40+
41+ const keyset = new Array < JWK > ( ) ;
42+
43+ for await ( const address of trusted ) {
44+ try {
45+ const response = await fetch ( address , { client } )
46+ console . log ( response . status )
47+ const data = await response . json ( ) as JSONWebKeySet ;
48+ keyset . push ( ...data . keys )
49+ } catch ( error ) {
50+ console . warn ( error )
51+ }
52+ }
53+ console . debug ( keyset ) ;
54+
55+ return keyset ;
56+ }
57+
58+ const jwk = async ( key : CryptoKey , ...jwks : Array < JWK > ) : Promise < JSONWebKeySet > => {
59+ const jwk = await exportJWK ( key ) ;
60+
61+ const keys = [ jwk , ...jwks ] . filter ( value => ! ! value ) ;
62+
63+ return { keys }
64+ }
65+
66+ const jwt = async ( key : CryptoKey , kid ?: string ) : Promise < string > => await new SignJWT ( claims )
67+ . setProtectedHeader ( { alg, kid } )
68+ . setAudience ( aud )
69+ . setIssuedAt ( )
70+ . setExpirationTime ( exp )
71+ . setSubject ( sub )
72+ . setIssuer ( iss )
73+ . sign ( key ) ;
74+
75+
76+ const write = async ( keys : JSONWebKeySet ) => {
77+ const file = await Deno . create ( path ) ;
78+ const encoder = new TextEncoder ( ) ;
79+ const data = encoder . encode ( JSON . stringify ( keys ) )
80+
81+ await file . write ( data )
82+ }
83+
84+ const app = new Hono ( ) ;
85+
86+ app . use ( '/jwks/.well-known/openid-configuration' , cors ( { origin } ) )
87+ app . get ( '/jwks/.well-known/openid-configuration' , ( context ) => {
88+ const keyset = localStorage . getItem ( 'jwk:val' ) ;
89+
90+ if ( keyset ) return context . json ( JSON . parse ( keyset ) ) ;
91+
92+ return context . json ( { message :'not found' } , 404 ) ;
93+ } ) ;
94+
95+ app . get ( '/' , ( context ) => context . redirect ( '/jwks/.well-known/openid-configuration' , 301 ) ) ;
96+ app . get ( '/jwks' , ( context ) => context . redirect ( '/jwks/.well-known/openid-configuration' , 301 ) ) ;
97+
98+ app . get ( '/anon' , async ( context ) => {
99+ const auth = context . req . header ( 'Authorization' ) ?. replace ( 'Bearer ' , '' ) ;
100+
101+ if ( api && api != auth ) return context . json ( { message :'not authorized' } , 401 )
102+
103+ const kid = localStorage . getItem ( 'jwk:kid' ) ?? '' ;
104+
105+ return context . json ( { access_token : await jwt ( keypair . privateKey , kid ) } )
106+ } ) ;
107+
108+ initialize ( ) . finally ( ( ) => console . log ( `initialized application with kid ${ localStorage . getItem ( 'kid' ) } ` ) ) ;
109+
110+ Deno . serve ( app . fetch ) ;
0 commit comments