Bubka / 2FAuth

A Web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes
https://docs.2fauth.app/
GNU Affero General Public License v3.0
2.15k stars 144 forks source link

gauth qr code can't be imported #244

Closed astrakid closed 4 months ago

astrakid commented 10 months ago

Version

4.2.4

Details & Steps to reproduce

1st way: open google authenticator, export all entries --> scan the qr code with 2fauth (import). error message: invalid or not readable data by google authenticator (translated from german) 2nd way: upload the screenshot from google authenticator -> same error message

Expectation

google authenticator entities are imported

Error & Logs

[2023-11-22 19:01:13] local.ERROR: Protobuf failed to get OTP parameters from provided migration URI  
[2023-11-22 19:01:13] local.ERROR: Error occurred during parsing: Unexpected wire type.  
[2023-11-22 19:01:13] local.ERROR: Google Authenticator {"userId":1,"exception":"[object] (App\\Exceptions\\InvalidMigrationDataException(code: 0): Google Authenticator at /srv/app/Services/Migrators/GoogleAuthMigrator.php:37)
[stacktrace]
#0 /srv/app/Services/TwoFAccountService.php(59): App\\Services\\Migrators\\GoogleAuthMigrator->migrate()
#1 /srv/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(353): App\\Services\\TwoFAccountService->migrate()
#2 /srv/app/Api/v1/Controllers/TwoFAccountController.php(134): Illuminate\\Support\\Facades\\Facade::__callStatic()
#3 /srv/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Api\\v1\\Controllers\\TwoFAccountController->migrate()
#4 /srv/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()
#5 /srv/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#6 /srv/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()
#7 /srv/vendor/laravel/framework/src/Illuminate/Routing/Router.php(799): Illuminate\\Routing\\Route->run()
#8 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#9 /srv/app/Http/Middleware/LogUserLastSeen.php(34): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#10 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): App\\Http\\Middleware\\LogUserLastSeen->handle()
#11 /srv/app/Http/Middleware/KickOutInactiveUser.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#12 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): App\\Http\\Middleware\\KickOutInactiveUser->handle()
#13 /srv/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#14 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()
#15 /srv/app/Http/Middleware/SetLanguage.php(68): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#16 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): App\\Http\\Middleware\\SetLanguage->handle()
#17 /srv/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#18 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Auth\\Middleware\\Authenticate->handle()
#19 /srv/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(159): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#20 /srv/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(135): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequest()
#21 /srv/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(87): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequestUsingNamedLimiter()
#22 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\ThrottleRequests->handle()
#23 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#24 /srv/vendor/laravel/framework/src/Illuminate/Routing/Router.php(800): Illuminate\\Pipeline\\Pipeline->then()
#25 /srv/vendor/laravel/framework/src/Illuminate/Routing/Router.php(777): Illuminate\\Routing\\Router->runRouteWithinStack()
#26 /srv/vendor/laravel/framework/src/Illuminate/Routing/Router.php(741): Illuminate\\Routing\\Router->runRoute()
#27 /srv/vendor/laravel/framework/src/Illuminate/Routing/Router.php(730): Illuminate\\Routing\\Router->dispatchToRoute()
#28 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()
#29 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#30 /srv/app/Http/Middleware/ForceJsonResponse.php(19): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#31 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): App\\Http\\Middleware\\ForceJsonResponse->handle()
#32 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#33 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#34 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#35 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#36 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#37 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#38 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#39 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#40 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(89): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#41 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#42 /srv/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(62): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#43 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()
#44 /srv/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#45 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#46 /srv/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#47 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()
#48 /srv/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#49 /srv/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()
#50 {main}
"}

Execution environment

AUTH_PROXY_HEADER_FOR_EMAIL=null REDIS_PORT=6379 LOG_LEVEL=notice DB_CONNECTION=sqlite APP_DEBUG=false MAIL_USERNAME=null WEBAUTHN_NAME=2FAuth HOSTNAME=314d82d55162 MAIL_FROM_ADDRESS=xxx@yyy.de APP_URL=http://localhost IS_DEMO_APP=false MIX_ENV=local SHLVL=1 PUSHER_APP_ID= HOME=/ PUSHER_APP_SECRET= BROADCAST_DRIVER=log COMMIT=c765bfd MAIL_FROM_NAME=2FAuth DB_DATABASE=/srv/database/database.sqlite APP_NAME=2FAuth MAIL_DRIVER=SMTP WEBAUTHN_ID=null WEBAUTHN_ICON=null SESSION_DRIVER=file WEBAUTHN_USER_VERIFICATION=preferred TRUSTED_PROXIES=null LOG_CHANNEL=daily MAIL_FROM=changeme@example.com VERSION=latest CREATED=2023-11-21T12:53:11Z CACHE_DRIVER=file LOGIN_THROTTLE=5 QUEUE_DRIVER=sync TERM=xterm MAIL_ENCRYPTION=null PUSHER_APP_KEY= PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SITE_OWNER=mail@example.com PUSHER_APP_CLUSTER=mt1 MAIL_PASSWORD=null MAIL_HOST=10.a.b.c THROTTLE_API=60 AUTHENTICATION_GUARD=web-guard SESSION_LIFETIME=120 REDIS_PASSWORD=null PROXY_LOGOUT_URL=null MAIL_PORT=2525 MIX_PUSHER_APP_KEY= APP_ENV=local APP_KEY=SomeRandomStringOf32CharsExactly REDIS_HOST=127.0.0.1 MIX_PUSHER_APP_CLUSTER= PWD=/srv AUTH_PROXY_HEADER_FOR_USER=null

Containerization

Additional information

No response

astrakid commented 10 months ago

image

astrakid commented 10 months ago

the qr-code contains: otpauth-migration://offline?data=Ck123456zy5Glu123456JlNsYW123456VkNDKT123456ZWdyb2123456NzhAZ2123456Y29tGg123456ayABKA123456[430charremoved]ChSjccaijm%2FJ6gqvp4%2FOSkHu5vKAXhIIbGlua2VkSW4gASgBMAIQARgBIAAo5J6%2Bqfr%2F%2F%2F%2F%2FAQ%3D%3D

Bubka commented 10 months ago

I just tested with GAuth 4.0.2 on iOS, no issue. What OS & GAuth version are you using?

astrakid commented 10 months ago

Android 13 (MIUI 14.0.8), Authenticator 6.0

juppwerner commented 10 months ago

Same error here, saying "not a valid QR code".

Android 10, Honor 8x, Google authenticator version 6.0

The QR Code contains: otpauth-migration::offline?data=...

Addition: I see this error in the running docker conatiner error output:

[error] 41#41: *27 FastCGI sent in stderr: "PHP message: PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 67108872 bytes) in /srv/vendor/khanamiryan/qrcode-detector-decoder/lib/GDLuminanceSource.php on line 85" while reading response header from upstream, client: 172.17.0.1, server: 2fauth, request: "POST /api/v1/qrcode/decode HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost:8081", referrer: "http://localhost:8081/account/import"

The image has a size of 376 KB.

The image was then cropped to only contain the QR code, is of the type jpg and has a size of 146 kB.

Error: "POST /api/v1/qrcode/decode HTTP/1.1" 400 44 "http://localhost:8081/account/import" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"

Thank you.

Regards Joachim

juppwerner commented 10 months ago

In addition to my report above:

I was able to sucessfully decode the Goggle Autheticator export QRCode screenshot using the khanamiryan/php-qrcode-detector-decoder library with this code:

$qrcode = new QrReader(__DIR__.'/Screenshot_20231124_084225_65.jpg');
$hints = [
    'TRY_HARDER' => true,
    'NR_ALLOW_SKIP_ROWS' => 0
];
$qrcode->decode($hints);
$text = $qrcode->text($hints); 

Maybe the $hints array should be added to the method app/Services/QrCodeService::decode() method() ?

Bubka commented 10 months ago

@juppwerner Thx for the hint. I will make some tests with these arguments, but before that I need to reproduce the issue and check exactly what is failing. The OP issue seems to be an error at data parsing while you seems to have an issue at QR decoding.

To you both: How many accounts were exported from Authenticator? With Dev Tools opened in your browser, what is the result of the POST request to .../api/v1/qrcode/decode right after you upload a (rejected) QR code image from the Import page?

astrakid commented 10 months ago

image i have 7 accounts in there. when just import 1 it is working. will now try to find the bad one.

astrakid commented 10 months ago

yes, the amount of exports seem to be the root cause. with 4 accounts it works, starting with 5 it doesn't recognize a live scan and an uploaded qr code will fail as well.

juppwerner commented 10 months ago

I have 9 accounts in my export QrCode. Just to clarify, I was not able to parse my qr code image to text without using the hints array.

Bubka commented 9 months ago

@juppwerner

Error Allowed memory size of 134217728 bytes exhausted (tried to allocate 67108872 bytes) in /srv/vendor/khanamiryan/qrcode-detector-decoder/lib/GDLuminanceSource.php on line 85 means the memory_limit set in php.ini has been reached by the script. So trying to decode an hi-res image may be the reason why you faced it.

How did you tried the following? Using a local dev env?

I was able to sucessfully decode the Goggle Autheticator export QRCode screenshot using the khanamiryan/php-qrcode-detector-decoder library with this code:


$qrcode = new QrReader(__DIR__.'/Screenshot_20231124_084225_65.jpg');
$hints = [
    'TRY_HARDER' => true,
    'NR_ALLOW_SKIP_ROWS' => 0
];

If so, the php config of your dev env may have a different memory_limit than the one in the docker container, making this test not really reliable. Correct me if I'm wrong, this is just a guess.

Furthermore, I don't understand which "version" of the qrcode you was able to decode with the $hints option: the original one or the cropped one?

Bubka commented 9 months ago

@astrakid

yes, the amount of exports seem to be the root cause. with 4 accounts it works, starting with 5 it doesn't recognize a live scan and an uploaded qr code will fail as well.

The live scan decoding is not done by the same part of 2FAuth as the decoding of the uploaded qrcodes. In the first case it is a local js qr reader, otherwise it is a php qr reader. This is why you received the error regardless of the method. So the pb comes from the part after the = sign of the otpauth migration uri which is encoded with protobuf.

i have 7 accounts in there. when just import 1 it is working. will now try to find the bad one.

Any news on that?

kenci commented 9 months ago

is it possible to increase the php memory limit in docker container?

astrakid commented 9 months ago

i have 7 accounts in there. when just import 1 it is working. will now try to find the bad one.

at least it was only the amount of imported codes causing the issue, so all codes worked at all. but not all 7 at once.

deanfourie1 commented 9 months ago

I too am unable to import all google account,

When exporting them, it provides 3 QR codes to scan for import, I cannt add these to 2fauthy as it doesnt like them and produces a error "Server Error"

ericsubach commented 7 months ago

Leaving this here for anyone who is also struggling with this issue. I had to use an app like QtQr to get the text from the QR code, then use this tool to convert the Google-specific transfer links to plain otpauth links that work on most authenticators. Supposedly the Aegis authenticator can understand the Google links natively, but I haven't confirmed.

Bubka commented 7 months ago

Thx for the head up and the workaround @ericsubach, I totally forgot this issue šŸ«¤ Will work on it asap.

xtay573269555 commented 6 months ago

Same issue here.

Bubka commented 6 months ago

So, I was able to reproduce the not a valid qr code error. This occurs when the uploaded image contains a qrcode which is too small or too blurry. I have changed the message in the error notification to be more relevant and to suggest how to fix the issue (cropping the image, submitting a sharper image).

I've also changed the way images with qr code are analyzed. Now, if no readable QR code is found in the image, the image is scanned again but with a more aggressive (but more expensive for the server) method. In some situations this should prevent the qrcode reading to fail.

Finally, for those who want to increase the memory limit no matter what, here is how to do so using docker-compose:

zzzdrv commented 6 months ago

same issue as OP

Ver 5.1.1 (Deployed on Ubuntu via Docker Compose. ) Authenticator 6.0

5 Google accounts, successfully exported 2. However, for the remaining accounts, whether exporting individually or in bulk, it consistently prompts "Invalid or unreadable Google Authenticator data".

Here's my workaround:

After exporting the Google QR code, I scanned it using the Edge browser on my phone to obtain the text (otpauth-migration://offline?data=xxxx), saved it as a text file, and successfully imported it.