Repository: lestrrat-go/jwx
Pull request #615 (2022-03-06 05:24:17)

document jku usage, tweak parameters

codecov-commenter: # [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) Report
> Merging [#615](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (4fa62db) into [v2](https://codecov.io/gh/lestrrat-go/jwx/commit/51565e68d57e1a7920a6c33bbd6ff7177360ebda?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (51565e6) will **decrease** coverage by `0.05%`.
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/lestrrat-go/jwx/pull/615/graphs/tree.svg?width=650&height=150&src=pr&token=kU2RSDosPt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)

```diff
@@            Coverage Diff             @@
##               v2     #615      +/-   ##
==========================================
- Coverage   72.31%   72.25%   -0.06%     
==========================================
  Files          88       84       -4     
  Lines        9775     9762      -13     
==========================================
- Hits         7069     7054      -15     
- Misses       1900     1902       +2     
  Partials      806      806              
```


| [Impacted Files](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) | Coverage Δ | |
|---|---|---|
| [jws/key\_provider.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andzL2tleV9wcm92aWRlci5nbw==) | `64.78% <0.00%> (ø)` | |
| [jws/options\_gen.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andzL29wdGlvbnNfZ2VuLmdv) | `71.42% <100.00%> (ø)` | |
| [internal/ecutil/ecutil.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-aW50ZXJuYWwvZWN1dGlsL2VjdXRpbC5nbw==) | `79.48% <0.00%> (-5.13%)` | :arrow_down: |
| [jws/es256k.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andzL2VzMjU2ay5nbw==) | | |
| [jwk/es256k.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL2VzMjU2ay5nbw==) | | |
| [jwa/secp2561k.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andhL3NlY3AyNTYxay5nbw==) | | |
| [internal/json/goccy.go](https://codecov.io/gh/lestrrat-go/jwx/pull/615/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-aW50ZXJuYWwvanNvbi9nb2NjeS5nbw==) | | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Last update [371df03...4fa62db](https://codecov.io/gh/lestrrat-go/jwx/pull/615?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).


Patch:
diff --git a/docs/01-jwt.md b/docs/01-jwt.md
index 16e90965..b6edb0ae 100644
--- a/docs/01-jwt.md
+++ b/docs/01-jwt.md
@@ -17,7 +17,6 @@ In this document we describe how to work with JWT using `github.com/lestrrat-go/
   * [Parse and Verify a JWT (with a key set, matching "kid")](#parse-and-verify-a-jwt-with-a-key-set-matching-kid)
   * [Parse and Verify a JWT (using arbitrary keys)](#parse-and-verify-a-jwt-using-arbitrary-keys)
   * [Parse and Verify a JWT (using key specified in "jku")](#parse-and-verify-a-jwt-using-key-specified-in-jku)
-  * [Parse and Verify a JWT (using custom key retrieval logic)](#parse-and-verify-a-jwt-using-custom-key-retrieval-logic)
 * [Validation](#jwt-validation)
   * [Detecting error types](#detecting-error-types)
 * [Serialization](#jwt-serialization)
@@ -516,41 +515,15 @@ source: [examples/jwt_parse_with_key_provider_example_test.go](https://github.co
 ## Parse and Verify a JWT (using key specified in "jku")
 
 You can parse JWTs using the JWK Set specified in the`jku` field in the JWS message by telling `jwt.Parse()` to
-use `jws.VerifyAuto()` instead of `jws.Verify()`:
+use `jws.VerifyAuto()` instead of `jws.Verify()`. This would effectively allow a JWS to be
+self-validating.
 
-```go
-token, _ := jwt.Parse(
-  src,
-  jwt.WithVerifyAuto(nil, jwt.WithFetchWhitelist(...)),
-)
-```
+<!-- INCLUDE(examples/jwt_parse_with_jku_example_test.go) -->
+<!-- END INCLUDE -->
 
 This feature must be used with extreme caution. Please see the caveats and fine prints
 in the documentation for `jws.VerifyAuto()`
 
-## Parse and Verify a JWT (using custom key retrieval logic)
-
-Consider a case where you want to load the key to verify a Token from a database.
-In this case you can use `jws.KeyProvider`:
-
-```go
-token, _ := jwt.Parse(
-  src,
-  jwt.WithKeyProvider(jwt.WithKeyProviderFunc(func(ctx context.Context, sink jws.KeySink, sig *jws.Signature, msg *jws.Message) error {
-    // Use current signature or the message as hint to
-    // look for the key, perhaps KeyID()
-    kid := sig.ProtectedHeaders().KeyID()
-    alg, key, err := loadKey(kid)
-    if err != nil {
-      return err
-    }
-
-    sink.Key(alg, key)
-    return nil
-  })),
-)
-```
-
 # JWT Validation
 
 To validate if the JWT's contents, such as if the JWT contains the proper "iss","sub","aut", etc, or the expiration information and such, use the [`jwt.Validate()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwt#Validate) function.
diff --git a/jws/key_provider.go b/jws/key_provider.go
index 50aabbd3..4d024371 100644
--- a/jws/key_provider.go
+++ b/jws/key_provider.go
@@ -195,7 +195,7 @@ type jkuProvider struct {
 func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signature, _ *Message) error {
 	kid := sig.ProtectedHeaders().KeyID()
 	if kid == "" {
-		return nil
+		return fmt.Errorf(`use of "jku" requires that the payload contain a "kid" field in the protected header`)
 	}
 
 	// errors here can't be reliablly passed to the consumers.
@@ -220,6 +220,7 @@ func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signatur
 
 	key, ok := set.LookupKeyID(kid)
 	if !ok {
+		// It is not an error if the key with the kid doesn't exist
 		return nil
 	}
 
diff --git a/jws/options.yaml b/jws/options.yaml
index 81946125..6dd99acc 100644
--- a/jws/options.yaml
+++ b/jws/options.yaml
@@ -129,7 +129,6 @@ options:
     argument_type: context.Context
   - ident: ProtectedHeaders
     interface: WithKeySuboption
-    option_name: WithProtected
     argument_type: Headers
     comment: |
       WithProtected is used with `jws.WithKey()` option when used with `jws.Sign()`
@@ -138,7 +137,6 @@ options:
       It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()`
   - ident: PublicHeaders
     interface: WithKeySuboption
-    option_name: WithPublic
     argument_type: Headers
     comment: |
       WithPublic is used with `jws.WithKey()` option when used with `jws.Sign()`
diff --git a/jws/options_gen.go b/jws/options_gen.go
index ae6ad034..06b6464a 100644
--- a/jws/options_gen.go
+++ b/jws/options_gen.go
@@ -150,11 +150,11 @@ func (identPretty) String() string {
 }
 
 func (identProtectedHeaders) String() string {
-	return "WithProtected"
+	return "WithProtectedHeaders"
 }
 
 func (identPublicHeaders) String() string {
-	return "WithPublic"
+	return "WithPublicHeaders"
 }
 
 func (identRequireKid) String() string {
@@ -246,7 +246,7 @@ func WithPretty(v bool) WithJSONSuboption {
 // to specify a protected header to be attached to the JWS signature.
 //
 // It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()`
-func WithProtected(v Headers) WithKeySuboption {
+func WithProtectedHeaders(v Headers) WithKeySuboption {
 	return &withKeySuboption{option.New(identProtectedHeaders{}, v)}
 }
 
@@ -257,7 +257,7 @@ func WithProtected(v Headers) WithKeySuboption {
 //
 // `jws.Sign()` will result in an error if `jws.WithPublic()` is used
 // and the serialization format is compact serialization.
-func WithPublic(v Headers) WithKeySuboption {
+func WithPublicHeaders(v Headers) WithKeySuboption {
 	return &withKeySuboption{option.New(identPublicHeaders{}, v)}
 }
 


Test patch:
diff --git a/examples/jwt_parse_with_jku_example_test.go b/examples/jwt_parse_with_jku_example_test.go
new file mode 100644
index 00000000..907b8712
--- /dev/null
+++ b/examples/jwt_parse_with_jku_example_test.go
@@ -0,0 +1,80 @@
+package examples_test
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+
+	"github.com/lestrrat-go/jwx/v2/jwa"
+	"github.com/lestrrat-go/jwx/v2/jwk"
+	"github.com/lestrrat-go/jwx/v2/jws"
+	"github.com/lestrrat-go/jwx/v2/jwt"
+)
+
+func ExampleJWT_ParseWithJKU() {
+	set := jwk.NewSet()
+
+	var signingKey jwk.Key
+
+	// for _, alg := range algorithms {
+	for i := 0; i < 3; i++ {
+		pk, err := rsa.GenerateKey(rand.Reader, 2048)
+		if err != nil {
+			fmt.Printf("failed to generate private key: %s\n", err)
+			return
+		}
+		// too lazy to write a proper algorithm. just assign every
+		// time, and signingKey will end up being the last key generated
+		privkey, err := jwk.New(pk)
+		if err != nil {
+			fmt.Printf("failed to create jwk.Key: %s\n", err)
+			return
+		}
+		privkey.Set(jwk.KeyIDKey, fmt.Sprintf(`key-%d`, i))
+
+		// It is important that we are using jwk.Key here instead of
+		// rsa.PrivateKey, because this way `kid` is automatically
+		// assigned when we sign the token
+		signingKey = privkey
+
+		pubkey, err := privkey.PublicKey()
+		if err != nil {
+			fmt.Printf("failed to create public key: %s\n", err)
+			return
+		}
+		set.Add(pubkey)
+	}
+
+	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+		json.NewEncoder(w).Encode(set)
+	}))
+	defer srv.Close()
+
+	// Create a JWT
+	token := jwt.New()
+	token.Set(`foo`, `bar`)
+
+	hdrs := jws.NewHeaders()
+	hdrs.Set(jws.JWKSetURLKey, srv.URL)
+
+	serialized, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, signingKey, jws.WithProtectedHeaders(hdrs)))
+	if err != nil {
+		fmt.Printf("failed to seign token: %s\n", err)
+		return
+	}
+
+	// We need to pass jwk.WithHTTPClient because we are using HTTPS,
+	// and we need the certificates setup
+	// We also need to explicitly setup the whitelist, this is required
+	tok, err := jwt.Parse(serialized, jwt.WithVerifyAuto(nil, jwk.WithHTTPClient(srv.Client()), jwk.WithFetchWhitelist(jwk.InsecureWhitelist{})))
+	if err != nil {
+		fmt.Printf("failed to verify token: %s\n", err)
+		return
+	}
+	_ = tok
+	// OUTPUT:
+}
diff --git a/examples/jwt_parse_with_key_provider_example_test.go b/examples/jwt_parse_with_key_provider_example_test.go
index d95aa365..ae204a09 100644
--- a/examples/jwt_parse_with_key_provider_example_test.go
+++ b/examples/jwt_parse_with_key_provider_example_test.go
@@ -55,6 +55,9 @@ func ExampleJWT_ParseWithKeyProvider() {
 			return nil
 		}
 
+		// Note: we only send one key here, but we could potentially send _ALL_
+		// keys in the store and have `jws.Verify()` try each one (but it would
+		// most likely be a waste if you did that)
 		sink.Key(alg, key)
 		return nil
 	})))
diff --git a/jws/jws_test.go b/jws/jws_test.go
index c6c12681..ff8e2b30 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -13,7 +13,6 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
-	"log"
 	"math/big"
 	"net/http"
 	"net/http/httptest"
@@ -1187,7 +1186,7 @@ func TestCustomField(t *testing.T) {
 	hdrs := jws.NewHeaders()
 	hdrs.Set(`x-birthday`, string(bdaybytes))
 
-	signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256, privkey, jws.WithProtected(hdrs)))
+	signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256, privkey, jws.WithProtectedHeaders(hdrs)))
 	if !assert.NoError(t, err, `jws.Sign should succeed`) {
 		return
 	}
@@ -1280,7 +1279,7 @@ func TestRFC7797(t *testing.T) {
 		hdrs.Set("b64", false)
 		hdrs.Set("crit", "b64")
 
-		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256, key, jws.WithProtected(hdrs)))
+		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256, key, jws.WithProtectedHeaders(hdrs)))
 		if !assert.Error(t, err, `jws.Sign should fail`) {
 			return
 		}
@@ -1291,7 +1290,7 @@ func TestRFC7797(t *testing.T) {
 		hdrs.Set("b64", false)
 		hdrs.Set("crit", "b64")
 
-		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256, key, jws.WithProtected(hdrs)), jws.WithDetachedPayload([]byte(payload)))
+		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256, key, jws.WithProtectedHeaders(hdrs)), jws.WithDetachedPayload([]byte(payload)))
 		if !assert.Error(t, err, `jws.Sign should fail`) {
 			return
 		}
@@ -1321,7 +1320,7 @@ func TestRFC7797(t *testing.T) {
 				hdrs.Set("crit", "b64")
 
 				payload := tc.Payload
-				signOptions := []jws.SignOption{jws.WithKey(jwa.HS256, key, jws.WithProtected(hdrs))}
+				signOptions := []jws.SignOption{jws.WithKey(jwa.HS256, key, jws.WithProtectedHeaders(hdrs))}
 				var verifyOptions []jws.VerifyOption
 				verifyOptions = append(verifyOptions, jws.WithKey(jwa.HS256, key))
 				if tc.Detached {
@@ -1672,7 +1671,7 @@ func TestJKU(t *testing.T) {
 					u += "?" + tc.Query
 				}
 				hdr.Set(jws.JWKSetURLKey, u)
-				signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256, key, jws.WithProtected(hdr)))
+				signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256, key, jws.WithProtectedHeaders(hdr)))
 				if !assert.NoError(t, err, `jws.Sign should succeed`) {
 					return
 				}
@@ -1735,7 +1734,9 @@ func TestJKU(t *testing.T) {
 			if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) {
 				return
 			}
-			log.Printf("key id -> %q", pubkey.KeyID())
+			if !assert.Equal(t, pubkey.KeyID(), key.KeyID(), `key ID should be populated`) {
+				return
+			}
 			set.Add(pubkey)
 		}
 		srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -1749,7 +1750,7 @@ func TestJKU(t *testing.T) {
 		for _, key := range keys {
 			hdr := jws.NewHeaders()
 			hdr.Set(jws.JWKSetURLKey, srv.URL)
-			signOptions = append(signOptions, jws.WithKey(jwa.RS256, key, jws.WithProtected(hdr)))
+			signOptions = append(signOptions, jws.WithKey(jwa.RS256, key, jws.WithProtectedHeaders(hdr)))
 		}
 
 		signed, err := jws.Sign(payload, signOptions...)
@@ -1803,6 +1804,11 @@ func TestJKU(t *testing.T) {
 					}
 				} else {
 					if !assert.NoError(t, err, `jws.Verify should succeed`) {
+						set, _ := jwk.Fetch(context.Background(), srv.URL, options...)
+						{
+							buf, _ := json.MarshalIndent(set, "", "  ")
+							t.Logf("%s", buf)
+						}
 						return
 					}
 					if !assert.Equal(t, payload, decoded, `decoded payload should match`) {
diff --git a/jws/options_gen_test.go b/jws/options_gen_test.go
index f12179ad..ece6527f 100644
--- a/jws/options_gen_test.go
+++ b/jws/options_gen_test.go
@@ -18,8 +18,8 @@ func TestOptionIdent(t *testing.T) {
 	require.Equal(t, "WithKeyUsed", identKeyUsed{}.String())
 	require.Equal(t, "WithMessage", identMessage{}.String())
 	require.Equal(t, "WithPretty", identPretty{}.String())
-	require.Equal(t, "WithProtected", identProtectedHeaders{}.String())
-	require.Equal(t, "WithPublic", identPublicHeaders{}.String())
+	require.Equal(t, "WithProtectedHeaders", identProtectedHeaders{}.String())
+	require.Equal(t, "WithPublicHeaders", identPublicHeaders{}.String())
 	require.Equal(t, "WithRequireKid", identRequireKid{}.String())
 	require.Equal(t, "WithSerialization", identSerialization{}.String())
 	require.Equal(t, "WithUseDefault", identUseDefault{}.String())
diff --git a/jws/signer_test.go b/jws/signer_test.go
index e532ae3e..267f24c3 100644
--- a/jws/signer_test.go
+++ b/jws/signer_test.go
@@ -83,8 +83,8 @@ func TestSignMulti(t *testing.T) {
 	v := strings.Join([]string{`{"iss":"joe",`, ` "exp":1300819380,`, ` "http://example.com/is_root":true}`}, "\r\n")
 	m, err := jws.Sign([]byte(v),
 		jws.WithJSON(),
-		jws.WithKey(jwa.RS256, rsakey, jws.WithPublic(s1hdr)),
-		jws.WithKey(jwa.ES256, dsakey, jws.WithPublic(s2hdr)),
+		jws.WithKey(jwa.RS256, rsakey, jws.WithPublicHeaders(s1hdr)),
+		jws.WithKey(jwa.ES256, dsakey, jws.WithPublicHeaders(s2hdr)),
 	)
 	if !assert.NoError(t, err, "jws.SignMulti should succeed") {
 		return
diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go
index 42b7e4b1..b944d7e7 100644
--- a/jwt/jwt_test.go
+++ b/jwt/jwt_test.go
@@ -249,7 +249,7 @@ func TestJWTParseVerify(t *testing.T) {
 						hdrs.Set(jws.KeyIDKey, kid)
 
 						t1 := jwt.New()
-						signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtected(hdrs)))
+						signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtectedHeaders(hdrs)))
 						if !assert.NoError(t, err, "token.Sign should succeed") {
 							return
 						}
@@ -342,7 +342,7 @@ func TestJWTParseVerify(t *testing.T) {
 		hdrs := jws.NewHeaders()
 		hdrs.Set(jws.KeyIDKey, kid)
 		t1 := jwt.New()
-		signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtected(hdrs)))
+		signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtectedHeaders(hdrs)))
 		if !assert.NoError(t, err, "token.Sign should succeed") {
 			return
 		}
@@ -707,7 +707,7 @@ func TestSignTyp(t *testing.T) {
 		t1 := jwt.New()
 		hdrs := jws.NewHeaders()
 		hdrs.Set(`typ`, `custom-typ`)
-		signed, err := jwt.Sign(t1, jwt.WithKey(jwa.RS256, key, jws.WithProtected(hdrs)))
+		signed, err := jwt.Sign(t1, jwt.WithKey(jwa.RS256, key, jws.WithProtectedHeaders(hdrs)))
 		if !assert.NoError(t, err) {
 			return
 		}
@@ -1384,7 +1384,7 @@ func TestVerifyAuto(t *testing.T) {
 	hdrs := jws.NewHeaders()
 	hdrs.Set(jws.JWKSetURLKey, srv.URL)
 
-	signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, key, jws.WithProtected(hdrs)))
+	signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, key, jws.WithProtectedHeaders(hdrs)))
 	if !assert.NoError(t, err, `jwt.Sign() should succeed`) {
 		return
 	}