@@ -5,41 +5,56 @@ import (
55"context"
66"crypto/ecdsa"
77"crypto/elliptic"
8+ "crypto/rsa"
89_ "embed"
910"encoding/json"
11+ "io"
1012"testing"
1113
1214"github.com/golang-jwt/jwt/v5"
13- "github.com/google/uuid"
1415"github.com/spf13/afero"
1516"github.com/stretchr/testify/assert"
1617"github.com/stretchr/testify/require"
1718"github.com/supabase/cli/internal/gen/signingkeys"
19+ "github.com/supabase/cli/internal/testing/fstest"
1820"github.com/supabase/cli/internal/utils"
1921"github.com/supabase/cli/pkg/config"
2022)
2123
2224func TestGenerateToken (t * testing.T ) {
25+ // Setup private key - ECDSA
26+ privateKeyECDSA , err := signingkeys .GeneratePrivateKey (config .AlgES256 )
27+ require .NoError (t , err )
28+ // Setup public key for validation
29+ publicKeyECDSA := ecdsa.PublicKey {Curve : elliptic .P256 ()}
30+ publicKeyECDSA .X , err = config .NewBigIntFromBase64 (privateKeyECDSA .X )
31+ require .NoError (t , err )
32+ publicKeyECDSA .Y , err = config .NewBigIntFromBase64 (privateKeyECDSA .Y )
33+ require .NoError (t , err )
34+
35+ // Setup private key - RSA
36+ privateKeyRSA , err := signingkeys .GeneratePrivateKey (config .AlgRS256 )
37+ require .NoError (t , err )
38+ // Setup public key for validation
39+ publicKeyRSA := rsa.PublicKey {}
40+ publicKeyRSA .N , err = config .NewBigIntFromBase64 (privateKeyRSA .Modulus )
41+ require .NoError (t , err )
42+ bigE , err := config .NewBigIntFromBase64 (privateKeyRSA .Exponent )
43+ require .NoError (t , err )
44+ publicKeyRSA .E = int (bigE .Int64 ())
45+
2346t .Run ("mints custom JWT" , func (t * testing.T ) {
2447claims := config.CustomClaims {
25- Role : "authenticated" ,
48+ IsAnon : true ,
49+ Role : "authenticated" ,
2650}
27- // Setup private key
28- privateKey , err := signingkeys .GeneratePrivateKey (config .AlgES256 )
29- require .NoError (t , err )
30- // Setup public key for validation
31- publicKey := ecdsa.PublicKey {Curve : elliptic .P256 ()}
32- publicKey .X , err = config .NewBigIntFromBase64 (privateKey .X )
33- require .NoError (t , err )
34- publicKey .Y , err = config .NewBigIntFromBase64 (privateKey .Y )
35- require .NoError (t , err )
3651// Setup in-memory fs
3752fsys := afero .NewMemMapFs ()
3853require .NoError (t , utils .WriteFile ("supabase/config.toml" , []byte (`
3954[auth]
4055signing_keys_path = "./keys.json"
4156` ), fsys ))
42- testKey , err := json .Marshal ([]config.JWK {* privateKey })
57+ testKey , err := json .Marshal ([]config.JWK {* privateKeyECDSA })
4358require .NoError (t , err )
4459require .NoError (t , utils .WriteFile ("supabase/keys.json" , testKey , fsys ))
4560// Run test
@@ -48,13 +63,13 @@ func TestGenerateToken(t *testing.T) {
4863// Check error
4964assert .NoError (t , err )
5065token , err := jwt .NewParser ().Parse (buf .String (), func (t * jwt.Token ) (any , error ) {
51- return & publicKey , nil
66+ return & publicKeyECDSA , nil
5267})
5368assert .NoError (t , err )
5469assert .True (t , token .Valid )
5570assert .Equal (t , map [string ]any {
5671"alg" : "ES256" ,
57- "kid" : privateKey .KeyID . String () ,
72+ "kid" : privateKeyECDSA .KeyID ,
5873"typ" : "JWT" ,
5974}, token .Header )
6075assert .Equal (t , jwt.MapClaims {
@@ -63,36 +78,116 @@ func TestGenerateToken(t *testing.T) {
6378}, token .Claims )
6479})
6580
66- t .Run ("mints legacy JWT" , func (t * testing.T ) {
81+ t .Run ("throws error on unsupported kty" , func (t * testing.T ) {
82+ claims := jwt.MapClaims {}
83+ // Setup in-memory fs
84+ fsys := afero .NewMemMapFs ()
85+ require .NoError (t , utils .WriteFile ("supabase/config.toml" , []byte (`
86+ [auth]
87+ signing_keys_path = "./keys.json"
88+ ` ), fsys ))
89+ testKey , err := json .Marshal ([]config.JWK {{KeyType : "oct" }})
90+ require .NoError (t , err )
91+ require .NoError (t , utils .WriteFile ("supabase/keys.json" , testKey , fsys ))
92+ // Run test
93+ err = Run (context .Background (), claims , io .Discard , fsys )
94+ // Check error
95+ assert .ErrorContains (t , err , "failed to convert JWK to private key: unsupported key type: oct" )
96+ })
97+
98+ t .Run ("accepts signing key from stdin" , func (t * testing.T ) {
6799utils .Config .Auth .SigningKeysPath = ""
68100utils .Config .Auth .SigningKeys = nil
69101claims := config.CustomClaims {
70- RegisteredClaims : jwt.RegisteredClaims {
71- Subject : uuid .New ().String (),
72- },
73- Role : "authenticated" ,
102+ Role : "service_role" ,
74103}
75104// Setup in-memory fs
76105fsys := afero .NewMemMapFs ()
106+ testKey , err := json .Marshal (privateKeyRSA )
107+ require .NoError (t , err )
108+ t .Cleanup (fstest .MockStdin (t , string (testKey )))
77109// Run test
78110var buf bytes.Buffer
79- err : = Run (context .Background (), claims , & buf , fsys )
111+ err = Run (context .Background (), claims , & buf , fsys )
80112// Check error
81113assert .NoError (t , err )
82114token , err := jwt .NewParser ().Parse (buf .String (), func (t * jwt.Token ) (any , error ) {
83- return [] byte ( utils . Config . Auth . JwtSecret . Value ) , nil
115+ return & publicKeyRSA , nil
84116})
85117assert .NoError (t , err )
86118assert .True (t , token .Valid )
87119assert .Equal (t , map [string ]any {
88- "alg" : "HS256" ,
120+ "alg" : "RS256" ,
121+ "kid" : privateKeyRSA .KeyID ,
89122"typ" : "JWT" ,
90123}, token .Header )
91124assert .Equal (t , jwt.MapClaims {
92- "exp" : float64 (1983812996 ),
93- "iss" : "supabase-demo" ,
94- "role" : "authenticated" ,
95- "sub" : claims .Subject ,
125+ "role" : "service_role" ,
96126}, token .Claims )
97127})
128+
129+ t .Run ("throws error on invalid key" , func (t * testing.T ) {
130+ claims := jwt.MapClaims {}
131+ // Setup in-memory fs
132+ fsys := afero .NewMemMapFs ()
133+ t .Cleanup (fstest .MockStdin (t , "" ))
134+ // Run test
135+ err = Run (context .Background (), claims , io .Discard , fsys )
136+ // Check error
137+ assert .ErrorContains (t , err , "failed to parse JWK: unexpected end of JSON input" )
138+ })
139+
140+ t .Run ("accepts kid from stdin" , func (t * testing.T ) {
141+ claims := jwt.MapClaims {
142+ "role" : "postgres" ,
143+ "sb-role" : "mgmt-api" ,
144+ }
145+ // Setup in-memory fs
146+ fsys := afero .NewMemMapFs ()
147+ require .NoError (t , utils .WriteFile ("supabase/config.toml" , []byte (`
148+ [auth]
149+ signing_keys_path = "./keys.json"
150+ ` ), fsys ))
151+ testKey , err := json .Marshal ([]config.JWK {
152+ * privateKeyECDSA ,
153+ * privateKeyRSA ,
154+ })
155+ require .NoError (t , err )
156+ require .NoError (t , utils .WriteFile ("supabase/keys.json" , testKey , fsys ))
157+ t .Cleanup (fstest .MockStdin (t , privateKeyRSA .KeyID ))
158+ // Run test
159+ var buf bytes.Buffer
160+ err = Run (context .Background (), claims , & buf , fsys )
161+ // Check error
162+ assert .NoError (t , err )
163+ token , err := jwt .NewParser ().Parse (buf .String (), func (t * jwt.Token ) (any , error ) {
164+ return & publicKeyRSA , nil
165+ })
166+ assert .NoError (t , err )
167+ assert .True (t , token .Valid )
168+ assert .Equal (t , map [string ]any {
169+ "alg" : "RS256" ,
170+ "kid" : privateKeyRSA .KeyID ,
171+ "typ" : "JWT" ,
172+ }, token .Header )
173+ assert .Equal (t , jwt.MapClaims {
174+ "role" : "postgres" ,
175+ "sb-role" : "mgmt-api" ,
176+ }, token .Claims )
177+ })
178+
179+ t .Run ("throws error on missing key" , func (t * testing.T ) {
180+ claims := jwt.MapClaims {}
181+ // Setup in-memory fs
182+ fsys := afero .NewMemMapFs ()
183+ require .NoError (t , utils .WriteFile ("supabase/config.toml" , []byte (`
184+ [auth]
185+ signing_keys_path = "./keys.json"
186+ ` ), fsys ))
187+ t .Cleanup (fstest .MockStdin (t , "test-key" ))
188+ // Run test
189+ err = Run (context .Background (), claims , io .Discard , fsys )
190+ // Check error
191+ assert .ErrorContains (t , err , "signing key not found: test-key" )
192+ })
98193}
0 commit comments