appwrite
appwrite copied to clipboard
π Bug Report: Password recovery url doesnβt generate well for flutter web
π 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?
- [X] I have read the Code of Conduct
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:
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
$url = Template::unParseURL($url);
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
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:
$url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]); $url = Template::unParseURL($url);
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 parsehttps://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.
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;
}
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.
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)
@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, there is no solution to using hash strategy at the moment.
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)?