Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage of json_encode() to convert time fractions to microseconds cause precision issues #709

Closed
sn-sharma opened this issue Mar 23, 2021 · 34 comments · Fixed by #710
Closed
Assignees
Labels
Milestone

Comments

@sn-sharma
Copy link

message: "Value is not in the allowed date format: 1616481863.528781890869140625"

@Slamdunk
Copy link
Collaborator

May you share with us:

  1. the version of lcobucci/jwt you are using
  2. whether the parsed token was created by lcobucci/jwt itself or by an external tool
  3. if the token was created by lcobucci/jwt, a snipped that reproduce the errored token

?

@lcobucci
Copy link
Owner

A stack trace and a sample token would also be quite helpful 👍

@RyanTheAllmighty
Copy link

RyanTheAllmighty commented Mar 23, 2021

Yup I'm also getting Value is not in the allowed date format: 1616497608.0510409.

Sample token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyNzZhM2JjOC1jY2JkLTRmMDMtYTlkMi1mZDcxZGYxOGZkZGYiLCJqdGkiOiJiYTFjZGFlZDkzYzEwZDc2ZTY2Y2Y5OGEwNTU0NWYxZDIzMzIyY2E4YzZlNDY0OGY4ZTUzOTAyZWVkYmZmYTY4ZDQ3MWJhZTNkZjIxYmNkMSIsImlhdCI6MTYxNjQ5NzUwOC43OTU2MTcsIm5iZiI6MTYxNjQ5NzUwOC43OTU2MiwiZXhwIjoxNjE2NTAxMTA4LjY4MTYzLCJzdWIiOiIzIiwic2NvcGVzIjpbImlkIl19.CeqmJ8VliEGLQVsqhmSTWHJcwtlYS0nMKkbLqMuLybqM2Bn_CxRzJEC4y3W3ez5OXf7W6aNSbMM4MEMFCSisT-sOZ1k_EudQSjA7lpWcWxw3J8j09H9EPanJnkPYYwuDnPQAiKkYpuZxD05aWAi-NnzBBh3Lq1PMScANQZE0uhWf_cpgnoHAS_b_wvjyBVn0PFxtIzi-qN8JP95Luvy3UfzrnsnWR7WeVdwe_ecx511E4asLTk_-fooRwklLW8qpuEvG1JnEpCEalnaRXrR2xUa9hIB6ogJnk4WWgWDbIyB0QubNBROfVaPXTbHppYENGROOJs8RCLi61Z9U_QkwO4nu54ZU-n9KWupUGgJMNV9ZJ9QOWDLT-7KlOumw8VzuZWIrZmYNjUmAoKkh7O_z6xrfXE1X1uUIgWbyxR9GgQgHTRmQZZtH93dhF-guYij6gZuRbJ723iQr-ddPV02h6bT1Z90dEpHRgPinx2VMZ_oNGpPTUlIX2ErqMdWQ5Cn5Wr6qYqYLsiaxxyQy2btU7FK3Fss3ifo5v1U5-Ay0uJhe5ygu49ymDraYNntdn05BpdTit-UiC2kPrtdX11eYiYZTrKkCymg5Qj5up1FZRTMxwmrr10dKOVAJ3cVRq5AZLRGTZ_ETTt8v1dfG5XKEOsisUibOEVn8ZU-kpgyc-O4

Downgrading from version 4.1.3 to 4.1.2 fixed the issue.

I use laravel/passport to generate the tokens which uses oauth2-server as the server which generates the tokens using lcobucci/jwt.

Not sure if this issue sits on the Laravel Passport implementation side (with oauth2-server package) or with this package, but again locking the version to 4.1.2 fixed it for me, and figured I'd pop a message on the issue for anyone else coming across this.

After reverting the version, this is the token I have:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyNzZhM2JjOC1jY2JkLTRmMDMtYTlkMi1mZDcxZGYxOGZkZGYiLCJqdGkiOiI0ZGFkMzFmZjE0YzZjNDhiZjFmNzgzNDA3NDdhMjE1MjZjM2UwNDU1OTZlNzIxZjAzZmIzNGM2MDg1ZWI0NzI4MjM1MTRlOGFmNmIyNDNkNCIsImlhdCI6IjE2MTY1MDM2MTkuOTcyNDMxIiwibmJmIjoiMTYxNjUwMzYxOS45NzI0MzciLCJleHAiOiIxNjE2NTA3MjE5Ljg1NDc1NCIsInN1YiI6IjEiLCJzY29wZXMiOlsiaWQiXX0.ZhpX_x6ahBCEP88NN45hdhK1-WbP7ga5YOYd3lnWcVch4o1Nn9NpQ6RQnPM8NmSKTy_Bv0EFWsSLBuJZunDSqm82LMgBFyWownUtG_ZaXRJXd2lORDxCQhbYs1uSWcufD64OJXmpj1GnjqZ7HexJxe7xHUUMrhaKELuulIBUpCcPPxWWksw-zN4Znm7gT71EY3Db_LrieOy0UdYDiYqPK4UoLavxk9z7ObXkePEXtaCPk0S_xsb4uWxpgWgnkt3pdf5E0L88o_maRYeLrzP42mi0Keq3c9K6-4tXopQIkJwpFc03DFFDZQCrCRvwK3Ot0cc_pFgQt78LpyRBukcXs01q_ACiG2WRNxOBP9hx582lciOAyp_FTPyXRaW14IE75otPrBYzFm0iQt_w3JSGkMsDhocM4h_IRPja4zUwlFLFTzLoT9mLaJVzrc9eo103YPGfnzSJyqgFoMJClVlyf7zNk0DWRCiucrFspE07rdTvn3Z6rPBGuMQnYe0WRMR8KX3pl1hokFlW7p8T2EEbo9o08QGnTnjnOjlWs6jIrHFuRszRXDY1plsL8XEjbfUlvHvM9JImo1cr3kRv1nqZgvYJZtNR6O5jwQjdxsT9IL7jwwG23jWMu7xuXCrVYwbnR_ZfOdE7oKg_B31cjBfP4YiGGxde-JMCnDAlO56JfkM

The only difference is as per the release notes, the timestamps are floats instead of strings now.

@Slamdunk
Copy link
Collaborator

Yup I'm also getting Value is not in the allowed date format: 1616497608.0510409.

Sample token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyNzZhM2JjOC1jY2JkLTRmMDMtYTlkMi1mZDcxZGYxOGZkZGYiLCJqdGkiOiJiYTFjZGFlZDkzYzEwZDc2ZTY2Y2Y5OGEwNTU0NWYxZDIzMzIyY2E4YzZlNDY0OGY4ZTUzOTAyZWVkYmZmYTY4ZDQ3MWJhZTNkZjIxYmNkMSIsImlhdCI6MTYxNjQ5NzUwOC43OTU2MTcsIm5iZiI6MTYxNjQ5NzUwOC43OTU2MiwiZXhwIjoxNjE2NTAxMTA4LjY4MTYzLCJzdWIiOiIzIiwic2NvcGVzIjpbImlkIl19.CeqmJ8VliEGLQVsqhmSTWHJcwtlYS0nMKkbLqMuLybqM2Bn_CxRzJEC4y3W3ez5OXf7W6aNSbMM4MEMFCSisT-sOZ1k_EudQSjA7lpWcWxw3J8j09H9EPanJnkPYYwuDnPQAiKkYpuZxD05aWAi-NnzBBh3Lq1PMScANQZE0uhWf_cpgnoHAS_b_wvjyBVn0PFxtIzi-qN8JP95Luvy3UfzrnsnWR7WeVdwe_ecx511E4asLTk_-fooRwklLW8qpuEvG1JnEpCEalnaRXrR2xUa9hIB6ogJnk4WWgWDbIyB0QubNBROfVaPXTbHppYENGROOJs8RCLi61Z9U_QkwO4nu54ZU-n9KWupUGgJMNV9ZJ9QOWDLT-7KlOumw8VzuZWIrZmYNjUmAoKkh7O_z6xrfXE1X1uUIgWbyxR9GgQgHTRmQZZtH93dhF-guYij6gZuRbJ723iQr-ddPV02h6bT1Z90dEpHRgPinx2VMZ_oNGpPTUlIX2ErqMdWQ5Cn5Wr6qYqYLsiaxxyQy2btU7FK3Fss3ifo5v1U5-Ay0uJhe5ygu49ymDraYNntdn05BpdTit-UiC2kPrtdX11eYiYZTrKkCymg5Qj5up1FZRTMxwmrr10dKOVAJ3cVRq5AZLRGTZ_ETTt8v1dfG5XKEOsisUibOEVn8ZU-kpgyc-O4

I'm sorry it seems to me the error you provided doesn't come from the token from the example.
The error has 1616497608.0510409 value but the example has 1616497508.795617, 1616497508.79562 and 1616501108.68163.

It is crucial for us to have the exact token that generates the issue: can you provide one of that kind please @RyanTheAllmighty ?

@sn-sharma
Copy link
Author

When you are trying to send the $date value in convertDate() function in Parser.php file. It is checking for string but the value is 1616497508.79562, which is float. Then you are trying to convert with json_encode function which return the value in string like "1616497508.79562548484415745842262445584". Now the createFormFormat not able to convert it into date and through the mentioned error.

@Slamdunk
Copy link
Collaborator

The example you are providing doesn't match my tries: https://3v4l.org/ukgjM

Can you give us the original JWT token?

@lcobucci
Copy link
Owner

lcobucci commented Mar 23, 2021

Maybe this is the cause/solution: https://3v4l.org/imDlQ

The re-encoded value (using JSON) has a different precision (goes beyond microsecond precision by 1 decimal place).
Using number_format() should mitigate that and we will have to accept that we might have a 1µs difference (negligible IMHO).

@sn-sharma
Copy link
Author

I think you got that.
This is the cause https://3v4l.org/3Hlth

You are trying to cast the value as string.

@lcobucci
Copy link
Owner

An alternative would be keeping the json_encode() but ensuring we don't have more than 6 decimal places (substr + strpos). I think number_format() should work just fine.

Can someone send a PR against v4.0.x? We should have the necessary tests in place to guard against issues.

@Slamdunk
Copy link
Collaborator

My question is: how did more than 6 decimal places end up into the token in the first place?

@lcobucci
Copy link
Owner

My question is: how did more than 6 decimal places end up into the token in the first place?

I'm asking myself the same question... but we can only tell that based on sample tokens.

@Slamdunk
Copy link
Collaborator

Hence my question to @RyanTheAllmighty who seems issuing the token from lcobucci/jwt itself

@lcobucci
Copy link
Owner

lcobucci commented Mar 23, 2021

https://3v4l.org/Xu3QH can be used to quickly detect the occurrence on a sample token btw.

@Slamdunk
Copy link
Collaborator

I'd suggest a bare base64_decode over the middle part of the token, without involving any json_* function: I don't trust them right now to provide me the correct raw informations ^^

@lcobucci
Copy link
Owner

lcobucci commented Mar 23, 2021

The problem is that var_dump() and (string) round the floats never mind, I see what you meant. https://3v4l.org/plOsL

@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

if you are using laravel/passport , so the problem come from that library , because it used firebase/jwt
and im not sur what firebase/jwt generate as timetamp

but when you generate token with this library and parse it , the are no issue , just to know at the moment you have
example "123456789.252500" the json_encode will remove 00 at end !

but to have more then 6 digits , i dont think it possible , all dates are formated by DateTimeImmutable ,

Just to know i tried 1millions of 6 digits with the new patch , so i think it will be from ~user code. i think you entred a long float number ~ / convertDate (return round float - find by @MarijnKoesen ) to the claims array

@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

I think you got that.
This is the cause https://3v4l.org/3Hlth

You are trying to cast the value as string.

 $claims[$claim] = $this->convertDate(is_string($date) ? $date : json_encode($date, JSON_THROW_ON_ERROR));

DateTimeImmutable::createFromFormat does not support more then 6 digits to format a date

I believe that number_format will resolve this issue 🙏

@lcobucci
Copy link
Owner

I'm pretty sure that number_format() will solve it. It's still worth understanding how this is being triggered.

@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

If anyone does not understand why its not working

https://3v4l.org/ahPuY

Read this:
1. json_encode round a float with more than 7 decimal places and can leave 7 decimals after rounding,
2. DateTimeImmutable::createFromFormat round (args float) who have more then 7 decimals to 6 decimals
3. DateTimeImmutable::createFromFormat recived 7 decimal because json_encode can have more 7 digits and false returned

Conclusion:
DateTimeImmutable::createFromFormat does not support more then 6 decimals in string format
but can round a float with more then 6 decimals


@lcobucci yeah , we need to know who generated the float with this long decimal sizes , @sachchida1993 can you show us that part of code if you can find it ! thx

@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

If im not wrong , DateTimeImmutable::createFromFormat format a string timestamp by cutting that string into 2 parts seconds + microseconds

thats why the microseconds was never rounded , because DateTimeImmutable::createFromFormat does not round those numbers (cutted strings)

ah , now its look very logic ,

so when DateTimeImmutable::createFromFormat recive a float, its convert it to string that make it round to 6 digits
but when its recived a string timestamp , its cut it and when the microseconds is more then 6 characters , he return that format is not valide (boolean: false)

_ U.u valid format :at least '.' + 1 digit present with max [0-9]*6 digits

@jariwiklund
Copy link

Pretty sure this line is to blame
https://github.com/lcobucci/jwt/blob/4.2.x/src/Encoding/MicrosecondBasedDateConversion.php#L35

@lcobucci
Copy link
Owner

Pretty sure this line is to blame
https://github.com/lcobucci/jwt/blob/4.2.x/src/Encoding/MicrosecondBasedDateConversion.php#L35

That's what I assume as well. However, this gives me an infinite loop (terrible code, I know):

<?php
do {
   $date = new DateTimeImmutable();
   $time = (float) $date->format('U.u');
   $formatted = json_encode($time, JSON_THROW_ON_ERROR);

   $decimalSeparatorPosition = strpos($formatted, '.');

   if ($decimalSeparatorPosition === false) { continue; }

   $microseconds = substr($formatted, $decimalSeparatorPosition + 1);

   var_dump($microseconds, $formatted, number_format($time, 6, '.', ''));
   echo PHP_EOL;
} while (strlen($microseconds) <= 6);


echo 'Issue found in:';
var_dump($date);

I have time now, so I can patch and release stuff but I still would like to see a token that has the problem.

@lcobucci lcobucci self-assigned this Mar 23, 2021
@lcobucci lcobucci added the Bug label Mar 23, 2021
@lcobucci lcobucci modified the milestones: 4.1.4, 4.0.3 Mar 23, 2021
@yassinrais
Copy link
Contributor

i dont think this test will throw any issue

@lcobucci
Copy link
Owner

i dont think this test will throw any issue

I'm shooting in the dark without having a token that presents the issue. String manipulation of the encoded float is the only way I can go about reproducing the problem.

I'm patching the thing nevertheless...

@lcobucci lcobucci changed the title InvalidTokenStructure : Value is not in the allowed date format Usage of json_encode() to convert time fractions to microseconds cause precision issues Mar 23, 2021
@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

@lcobucci dont forgot the test i did before : https://3v4l.org/XKHV4 (all the 6 digits possible)

Else to be more sur , use this more fast & full testing , then randomizing with repeated values
(1616536852.000001 -> +1616536852.999999)

$i=0;

do {
    $i++;

    $timestring = "1616536852." . str_pad("$i", 6, "0", STR_PAD_LEFT);

    $time = (float) $timestring;
    $formatted = json_encode($time, JSON_THROW_ON_ERROR);

    $decimalSeparatorPosition = strpos($formatted, '.');

    if ($decimalSeparatorPosition === false) {
        continue;
    }

    $microseconds = substr($formatted, $decimalSeparatorPosition + 1);
} while (strlen($microseconds) <= 6);

echo 'Issue found in if its not i> 1 000 001:';
var_dump($timestring, $microseconds, $formatted, number_format($time, 10, '.', ''));

@yassinrais
Copy link
Contributor

i will do another test with encoding and decoding jwt generated by this library ,
because i feel that this was not generated by this library 😳

@lcobucci
Copy link
Owner

This is interesting, though, the following test passes with no error:

/**
 * @test
 * @dataProvider timeFractionConversions
 */
public function floatsDoNotCauseParsingErrors(float $issuedAt, string $timeFraction): void
{
    $encoder = new JoseEncoder();

    $headers = $encoder->base64UrlEncode($encoder->jsonEncode(['typ' => 'JWT', 'alg' => 'none']));
    $claims = $encoder->base64UrlEncode($encoder->jsonEncode(['iat' => $issuedAt]));

    $config = Configuration::forUnsecuredSigner();
    $parsedToken = $config->parser()->parse($headers . '.' . $claims . '.');

    self::assertInstanceOf(Plain::class, $parsedToken);
    self::assertSame($timeFraction, $parsedToken->claims()->get('iat')->format('U.u'));
}

public function timeFractionConversions(): iterable
{
    yield [1616481863.528781890869140625, '1616481863.528782'];
    yield [1616497608.0510409, '1616497608.051041'];
}

@RyanTheAllmighty
Copy link

RyanTheAllmighty commented Mar 23, 2021

Yeah I wasn't sure if this was caused by this library itself or one of the other packages I use for the oauth2 server handling. I figured since the downgrade fixed it, it could be here. I've dived into the packages I use and they seem to not be doing anything other than passing the JWT onto this package, but it's possible with all the abstractions in the packages that I missed a step.

I don't have an example JWT anymore unfortunately. Looks like the token I gave wasn't one that was erroring. It seems as though this was happening when one of the timestamps went into 7 precision digits. All the stacktraces in my logs all mention failing to parse date formats all with 7 precision digits, and from memory this was happening on every single JWT token that I was getting back (again not sure whose end this is on, AFAIK the generation was on this packages side):

[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616457346.3878131 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)
[stacktrace]
#0 /my-application-path-here/vendor/lcobucci/jwt/src/Token/Parser.php(130): Lcobucci\\JWT\\Token\\InvalidTokenStructure::dateIsNotParseable('1616457346.3878...')
#1 /my-application-path-here/vendor/lcobucci/jwt/src/Token/Parser.php(114): Lcobucci\\JWT\\Token\\Parser->convertDate('1616457346.3878...')
#2 /my-application-path-here/vendor/lcobucci/jwt/src/Token/Parser.php(38): Lcobucci\\JWT\\Token\\Parser->parseClaims('eyJhdWQiOiIyNzZ...')
#3 /my-application-path-here/vendor/league/oauth2-server/src/AuthorizationValidators/BearerTokenValidator.php(99): Lcobucci\\JWT\\Token\\Parser->parse('eyJ0eXAiOiJKV1Q...')
... framework stuff

These were the specific starts of the stacktraces I have in my logs:

[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616457346.3878131 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)
[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616457354.7497079 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)
[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616457362.7197959 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)
[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616497082.4421661 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)
[previous exception] [object] (Lcobucci\\JWT\\Token\\InvalidTokenStructure(code: 0): Value is not in the allowed date format: 1616497608.0510409 at /my-application-path-here/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php:23)

If it's not the case that the issue sits here, I'll investigate the downstream packages further for the cause. A reverting of the version solves the immediate issue for me so I'm not in a rush thankfully.

@lcobucci
Copy link
Owner

Adding yield [1616536852.1000001, '1616536852.100000']; does make it fail 👍

@yassinrais
Copy link
Contributor

yassinrais commented Mar 23, 2021

Example of generating and parsing tests

<?php
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\UnencryptedToken;

require './vendor/autoload.php';


$config = Configuration::forSymmetricSigner(
    // You may use any HMAC variations (256, 384, and 512)
    new Sha256(),
    // replace the value below with a key of your own!
    InMemory::plainText('test')
    // You may also override the JOSE encoder/decoder if needed by providing extra arguments here
);


assert($config instanceof Configuration);


function gtoken($config)
{
    $now   = new DateTimeImmutable();
    return $config->builder()
                // Configures the issuer (iss claim)
                ->issuedBy('http://example.com')
                // Configures the audience (aud claim)
                ->permittedFor('http://example.org')
                // Configures the id (jti claim)
                ->identifiedBy('4f1g23a12aa')
                // Configures the time that the token was issue (iat claim)
                ->issuedAt($now)
                // Configures the time that the token can be used (nbf claim)
                ->canOnlyBeUsedAfter($now->modify('+'.rand(0, 999999999).' msec'))
                // Configures the expiration time of the token (exp claim)
                ->expiresAt($now->modify('+'.rand(0, 999999999).' msec'))
                // Configures a new claim, called "uid"
                ->withClaim('uid', 1)
                // Configures a new header, called "foo"
                ->withHeader('foo', 'bar')
                // Builds a new token
                ->getToken($config->signer(), $config->signingKey())
                ->toString();
}

function pToken($config, $tkn)
{
    $token = $config->parser()->parse($tkn);

    assert($token instanceof UnencryptedToken);

    return $token;
}

do {
    
    // generate random token
    $token = gtoken($config);

    // parse
    $parsed = pToken($config, $token);


    $timestring = $parsed->claims()->get('iat')->format('U.u');
    
    if (strpos($timestring, '.') === false) {
        continue;
    }

    $timefloat = (float) $timestring;

    $formatted = json_encode($timefloat, JSON_THROW_ON_ERROR);
    $decimalSeparatorPosition = strpos($formatted, '.');

    if ($decimalSeparatorPosition === false) {
        continue;
    }

    $microseconds = substr($formatted, $decimalSeparatorPosition + 1);
} while (strlen($microseconds) <= 6 && $timestring === (str_pad(json_encode($timefloat), 17, '0')));

echo 'Issue found in if its not i> 1 000 001:';
var_dump($token, $timestring, $microseconds, $formatted, number_format($timefloat, 6, '.', ''));

I ran it for a while and found no problems 🤔.

@yassinrais
Copy link
Contributor

the are no way that this library will generate more then 6 decimals 🤔

Adding yield [1616536852.1000001, '1616536852.100000']; does make it fail 👍

the only issue we have , the parser of 7 decimals

@lcobucci
Copy link
Owner

the are no way that this library will generate more then 6 decimals

Adding yield [1616536852.1000001, '1616536852.100000']; does make it fail +1

the only issue we have , the parser of 7 decimals

I couldn't find either... but #710 should mitigate the problem. Can we get some reviews?

@lcobucci lcobucci linked a pull request Mar 23, 2021 that will close this issue
@lcobucci
Copy link
Owner

Alright, releasing new patches now. I'd still love it if you could send a sample token that presents the issue @sachchida1993.

@lcobucci
Copy link
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants