appwrite icon indicating copy to clipboard operation
appwrite copied to clipboard

πŸ› Bug Report: Password recovery url doesn’t generate well for flutter web

Open Silfalion opened this issue 3 years ago β€’ 8 comments

πŸ‘Ÿ Reproduction steps

-call the createRecovery method of the appwrite flutter sdk, providing a valid email and url -the url should be under the format https://domain/#/recovery -receive the email

πŸ‘ Expected behavior

A url that looks like https://domain/#/recovery?userId=76887&secret=877797......&expires=77686... so basically the url we gave plus the query parameters

πŸ‘Ž Actual Behavior

https://domain/userId=76887&secret=877797......&expires=77686...#/recovery so it adds the query parameters right in the middle of the original url

🎲 Appwrite version

Different version (specify in environment)

πŸ’» Operating system

Linux

🧱 Your Environment

0.11.0 appwrite version

πŸ‘€ Have you spent some time to check if this issue has been raised before?

  • [X] I checked and didn't find similar issue

🏒 Have you read the Code of Conduct?

Silfalion avatar Nov 09 '21 17:11 Silfalion

This is tough because, typically, anything after # is a fragment and is added at the end. The part of the code causing this unexpected behavior is:

account.php:

        $url = Template::parseURL($url);
        $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
        $url = Template::unParseURL($url);

Template.php:

    public static function parseURL($url)
    {
        return \parse_url($url);
    }
    // ...
    public static function unParseURL(array $url)
    {
        $scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';
        $host = isset($url['host']) ? $url['host'] : '';
        $port = isset($url['port']) ? ':'.$url['port'] : '';

        $user = isset($url['user']) ? $url['user'] : '';
        $pass = isset($url['pass']) ? ':'.$url['pass'] : '';
        $pass = ($user || $pass) ? "$pass@" : '';

        $path = isset($url['path']) ? $url['path'] : '';
        $query = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';

        $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';

        return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
    }

parse_url() is a standard PHP function that will parse https://domain/#/recovery into:

Array
(
    [scheme] => https
    [host] => domain
    [path] => /
    [fragment] => /recovery
)

Playground: https://www.tehplayground.com/bB8AM4EMuBy1ECQH

stnguyen90 avatar Nov 09 '21 17:11 stnguyen90

This is tough because, typically, anything after # is a fragment and is added at the end. The part of the code causing this unexpected behavior is:

account.php:


        $url = Template::parseURL($url);

        $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);

        $url = Template::unParseURL($url);

Template.php:


    public static function parseURL($url)

    {

        return \parse_url($url);

    }

    // ...

    public static function unParseURL(array $url)

    {

        $scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';

        $host = isset($url['host']) ? $url['host'] : '';

        $port = isset($url['port']) ? ':'.$url['port'] : '';



        $user = isset($url['user']) ? $url['user'] : '';

        $pass = isset($url['pass']) ? ':'.$url['pass'] : '';

        $pass = ($user || $pass) ? "$pass@" : '';



        $path = isset($url['path']) ? $url['path'] : '';

        $query = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';



        $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';



        return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;

    }

parse_url() is a standard PHP function that will parse https://domain/#/recovery into:


Array

(

    [scheme] => https

    [host] => domain

    [path] => /

    [fragment] => /recovery

)

Playground: https://www.tehplayground.com/bB8AM4EMuBy1ECQH

I understand. Setting the url strategy would be a good solution though without using a personal domain services like Firebase dont recognize the URL. I'm trying to find a fix for it at the moment. Will update as soon as I do.

Silfalion avatar Nov 10 '21 16:11 Silfalion

Hi @Silfalion,

I had the same issue for Flutter web, I solved it using the path URL strategy.

void main() {
  setUrlStrategy(PathUrlStrategy());
  runApp(const MyApp());
}

To set the verification link, I'm using this code which also works while testing at localhost because it preserves the port number.

const String verificationRoute = '/verification'

String verificationURL() {
  return replaceFromLastStringURL(verificationRoute);
}

String replaceFromLastStringURL(String replaceBy, {String lastSymbol = '/'}) {
  String url = Uri.base.toString();
  int index = url.lastIndexOf(lastSymbol);
  url = url.substring(0, index) + replaceBy;
  return url;
}

JaviBonilla avatar Nov 13 '21 11:11 JaviBonilla

Thank you so much for reminding me @JaviBonilla, was gonna post my solution but forgot. I used something very similar as you with the url_strategy package.

But the problem then became that Firebase didn't find the path, it's a little unrelated to the issue but if anyone else needs a fix use this in your firebase.json:

{
  "hosting": {
    "cleanUrls": true,
    "public": "build/web",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

The important part is the rewrites field.

Thank you both for your help on this issue.

Silfalion avatar Nov 13 '21 12:11 Silfalion

Hi! I'm having the same issue related with Appwrite password recovery URL and Flutter Web Hash URLStrategy

Unfortunately, I can't use the workaround mentioned above (changing the URLStrategy from Hash to Path) because of the hosting I'm using (it would require additional configuration to serve index.html to every route request, and this is not supported).

In addition, Hash URLStrategy is the default one in Flutter, so I think Appwrite should support it as well (without obviously cutting out support for the Path one which is the only one currently supported)

LeonardoMantovani avatar Nov 02 '22 18:11 LeonardoMantovani

@stnguyen90, @eldadfux, sorry for tagging you but since you were following me on the old issue (#4585) we closed to continue in this one I wanted to make sure you were on this too.

How should I proceed to implement password recovery in my flutter project keeping Hash URLStrategy?

LeonardoMantovani avatar Nov 05 '22 08:11 LeonardoMantovani

@LeonardoMantovani, there is no solution to using hash strategy at the moment.

stnguyen90 avatar Nov 05 '22 14:11 stnguyen90

Can we add a setting or a environment flag to Appwrite in order to support the hash strategy (or add a url param to the method)?

Gustl22 avatar Feb 15 '24 18:02 Gustl22