mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-05-24 23:28:54 -05:00
[client] WIP: web oauth login
This commit is contained in:
@@ -127,8 +127,10 @@ class PhylumRouteInformationParser extends RouteInformationParser<PhylumRoute> {
|
||||
}
|
||||
} else if (segments.length == 2) {
|
||||
if (segments[0] == 'login' && segments[1] == 'oauth') {
|
||||
print(uri.toString());
|
||||
return SynchronousFuture(OAuthLoginRoute(
|
||||
code: uri.queryParameters['code'] ?? '',
|
||||
state: uri.queryParameters['state'] ?? '',
|
||||
));
|
||||
}
|
||||
if (segments[0] == 'folder') {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
@@ -11,6 +10,7 @@ import 'package:phylum/libphylum/requests/shared_resources_request.dart';
|
||||
import 'package:phylum/libphylum/responses/responses.dart';
|
||||
import 'package:phylum/ui/layout/app_layout.dart';
|
||||
import 'package:phylum/ui/login/login_page.dart';
|
||||
import 'package:phylum/ui/login/oauth_login_page.dart';
|
||||
import 'package:phylum/ui/login/reset_password_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -24,15 +24,19 @@ sealed class PhylumRoute {
|
||||
|
||||
class OAuthLoginRoute extends PhylumRoute {
|
||||
final String code;
|
||||
final String state;
|
||||
|
||||
OAuthLoginRoute({required this.code});
|
||||
OAuthLoginRoute({required this.code, required this.state});
|
||||
|
||||
@override
|
||||
Uri get uri => Uri(path: '/login/oauth');
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context) {
|
||||
return const LoginPage();
|
||||
return OAuthLoginPage(
|
||||
code: code,
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +75,7 @@ sealed class ExplorerRoute extends PhylumRoute {
|
||||
context.select<AccountManagerState<PhylumAccount>, PhylumAccount?>((state) => state.selectedAccount);
|
||||
|
||||
if (account == null) {
|
||||
return kDebugMode ? const LoginPage() : LoginPage(fixedInstanceUrl: Uri());
|
||||
return const LoginPage();
|
||||
}
|
||||
return AppLayout.create(account);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ class InstanceConfig {
|
||||
final bool magicLink;
|
||||
final List<OpenIDProvider> openIDProviers;
|
||||
|
||||
bool get hasEmailLogin => password || magicLink;
|
||||
|
||||
InstanceConfig(
|
||||
{required this.url,
|
||||
required this.password,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:phylum/ui/login/instance_config.dart';
|
||||
import 'package:phylum/util/dialogs.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
|
||||
import 'package:phylum/util/base_uri_stub.dart' if (dart.library.js_interop) 'package:phylum/util/base_uri_web.dart';
|
||||
|
||||
class InstanceUrlFragment extends StatefulWidget {
|
||||
final Function(InstanceConfig) onInstanceSelected;
|
||||
|
||||
@@ -15,35 +16,30 @@ class InstanceUrlFragment extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _InstanceUrlFragmentState extends State<InstanceUrlFragment> {
|
||||
Uri? _url;
|
||||
Uri? _url = webAppBaseUri;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
TextField(
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
label: const Text('Instance URL'),
|
||||
hintText: 'https://phylum.example.com',
|
||||
),
|
||||
initialValue: webAppBaseUri?.toString(),
|
||||
autofocus: true,
|
||||
autofillHints: const [AutofillHints.url],
|
||||
keyboardType: TextInputType.url,
|
||||
onChanged: (value) {
|
||||
final Uri? url;
|
||||
if (kIsWeb) {
|
||||
url = value == '' ? Uri() : Uri.tryParse(value);
|
||||
} else {
|
||||
final tempUrl = Uri.tryParse(value);
|
||||
if (tempUrl != null && (kIsWeb || tempUrl.hasAuthority && tempUrl.hasScheme)) {
|
||||
url = tempUrl;
|
||||
} else {
|
||||
url = null;
|
||||
}
|
||||
var url = value == '' ? webAppBaseUri : Uri.tryParse(value);
|
||||
if (url != null && (!url.hasAuthority || !url.hasScheme)) {
|
||||
url = null;
|
||||
}
|
||||
setState(() => _url = url);
|
||||
},
|
||||
onSubmitted: _url == null ? null : (value) => checkConfig(_url!),
|
||||
onFieldSubmitted: _url == null ? null : (value) => checkConfig(_url!),
|
||||
),
|
||||
ElevatedButton(onPressed: _url == null ? null : () => checkConfig(_url!), child: Text('Next'))
|
||||
],
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/libphylum/responses/responses.dart';
|
||||
import 'package:phylum/ui/app/router.dart';
|
||||
import 'package:phylum/ui/app/routes.dart';
|
||||
import 'package:phylum/ui/common/logo_row.dart';
|
||||
import 'package:phylum/ui/login/instance_config.dart';
|
||||
import 'package:phylum/ui/login/instance_url_fragment.dart';
|
||||
import 'package:phylum/ui/login/password_login_fragment.dart';
|
||||
import 'package:phylum/util/dialogs.dart';
|
||||
import 'package:phylum/util/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
|
||||
import 'request.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
final Uri? fixedInstanceUrl;
|
||||
|
||||
const LoginPage({super.key, this.fixedInstanceUrl});
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
@@ -71,73 +55,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
return PasswordLoginFragment(
|
||||
onBackPressed: () => setState(() => _instanceConfig = null),
|
||||
instanceUrl: instanceConfig.url,
|
||||
requestLogin: instanceConfig.password
|
||||
? (email, password) => _performLogin(context, instanceConfig.url, email, password)
|
||||
: null,
|
||||
requestPasswordReset:
|
||||
instanceConfig.passwordReset ? (email) => _requestPasswordReset(context, instanceConfig.url, email) : null,
|
||||
resetPassword: instanceConfig.passwordReset
|
||||
? (email, token) => context
|
||||
.read<PhylumRouterDelegate>()
|
||||
.go(ResetPasswordRoute(instanceUri: instanceConfig.url, email: email, token: token))
|
||||
: null,
|
||||
openIDProviders: instanceConfig.openIDProviers,
|
||||
instanceConfig: instanceConfig,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _performLogin(BuildContext context, Uri instanceUri, String email, String password) async {
|
||||
final builder = UriBuilder.fromUri(instanceUri);
|
||||
builder.path = '${builder.path}/api/v1/auth/password';
|
||||
final request = MultipartRequest('post', builder.build());
|
||||
request.fields['email'] = email;
|
||||
request.fields['password'] = password;
|
||||
|
||||
final responseString = await sendRequest(context, 'Logging In', request);
|
||||
if (responseString == null) return;
|
||||
if (context.mounted) {
|
||||
final accountManager = context.read<AccountManager<PhylumAccount>>();
|
||||
final navigator = Navigator.of(context);
|
||||
showProgressDialog(context, barrierDismissible: false, message: 'Logging In');
|
||||
try {
|
||||
final response =
|
||||
BootstrapLoginResponse.fromResponse((jsonDecode(responseString) as Map).cast<String, dynamic>());
|
||||
final account = await PhylumAccount.create(
|
||||
serverUrl: instanceUri,
|
||||
accessToken: response.accessToken!,
|
||||
user: response.user,
|
||||
);
|
||||
await response.process(account);
|
||||
await accountManager.addAccount(account);
|
||||
navigator.pop();
|
||||
} catch (e, stack) {
|
||||
navigator.pop();
|
||||
if (context.mounted) {
|
||||
showAlertDialog(
|
||||
context,
|
||||
barrierDismissible: false,
|
||||
title: 'Error',
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
logger.w('Login Error', error: e, stackTrace: stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _requestPasswordReset(BuildContext context, Uri instanceUrl, String email) async {
|
||||
final builder = UriBuilder.fromUri(instanceUrl);
|
||||
builder.path = '${builder.path}/api/v1/auth/request-password-reset';
|
||||
final request = MultipartRequest('post', builder.build());
|
||||
request.fields['email'] = email;
|
||||
|
||||
final responseString = await sendRequest(context, 'Requesting Password Reset', request);
|
||||
if (responseString == null) return;
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
title: 'Password Reset Requested',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/libphylum/responses/responses.dart';
|
||||
import 'package:phylum/ui/app/router.dart';
|
||||
import 'package:phylum/ui/app/routes.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
import 'package:phylum/ui/app/dialog_scaffold.dart';
|
||||
|
||||
class OAuthLoginPage extends StatefulWidget {
|
||||
final String code;
|
||||
final String state;
|
||||
|
||||
const OAuthLoginPage({
|
||||
super.key,
|
||||
required this.code,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OAuthLoginPage> createState() => _OAuthLoginPageState();
|
||||
}
|
||||
|
||||
class _OAuthLoginPageState extends State<OAuthLoginPage> {
|
||||
String? error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
performLogin(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PhylumDialogScaffold(
|
||||
child: error == null
|
||||
? const Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
Text('Logging In'),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Text('Error: $error'),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<PhylumRouterDelegate>().go(ExplorerRouteHome());
|
||||
},
|
||||
child: Text('OK'))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void performLogin(BuildContext context) async {
|
||||
final instanceUrl = Uri.parse(widget.state);
|
||||
final builder = UriBuilder.fromUri(instanceUrl);
|
||||
builder.path = '${builder.path}/api/v1/auth/oauth';
|
||||
final request = MultipartRequest('post', builder.build());
|
||||
request.fields['code'] = widget.code;
|
||||
|
||||
try {
|
||||
final response = await Client().send(request);
|
||||
final responseString = await response.stream.bytesToString();
|
||||
final responseMap = (jsonDecode(responseString) as Map).cast<String, dynamic>();
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
if (context.mounted) {
|
||||
final navigator = Navigator.of(context);
|
||||
final routerDelegate = context.read<PhylumRouterDelegate>();
|
||||
final accountManager = context.read<AccountManager<PhylumAccount>>();
|
||||
|
||||
final response = BootstrapLoginResponse.fromResponse(responseMap);
|
||||
final account = await PhylumAccount.create(
|
||||
serverUrl: instanceUrl,
|
||||
accessToken: response.accessToken!,
|
||||
user: response.user,
|
||||
);
|
||||
await response.process(account);
|
||||
await accountManager.addAccount(account);
|
||||
navigator.pop();
|
||||
routerDelegate.go(ExplorerRouteHome());
|
||||
}
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
setState(() => error = responseMap['msg']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} on SocketException {
|
||||
if (context.mounted) {
|
||||
setState(() => error = 'unable to reach server');
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
setState(() => error = e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
void startOAuthFlow(Uri oAuthUrl) => throw UnimplementedError();
|
||||
import 'package:uri/uri.dart';
|
||||
|
||||
void startOAuthFlow(UriBuilder oAuthUriBuilder, Uri instanceUrl) => throw UnimplementedError();
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import 'package:uri/uri.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
void startOAuthFlow(Uri oAuthUrl) => launchUrl(oAuthUrl);
|
||||
void startOAuthFlow(UriBuilder oAuthUriBuilder, Uri instanceUrl) {
|
||||
final redirectBuilder = UriBuilder.fromUri(instanceUrl);
|
||||
// This will redirect to the native scheme cloud.phylum.drive://
|
||||
redirectBuilder.path = '/api/v1/auth/oauth_native_redirect';
|
||||
oAuthUriBuilder.queryParameters['redirect_uri'] = redirectBuilder.toString();
|
||||
launchUrl(oAuthUriBuilder.build());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import 'package:phylum/util/base_uri_web.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
void startOAuthFlow(Uri oAuthUrl) => web.window.location.href = oAuthUrl.toString();
|
||||
void startOAuthFlow(UriBuilder oAuthUriBuilder, Uri instanceUrl) {
|
||||
final redirectBuilder = UriBuilder.fromUri(webAppBaseUri);
|
||||
redirectBuilder.path = '/login/oauth';
|
||||
oAuthUriBuilder.queryParameters['redirect_uri'] = redirectBuilder.toString();
|
||||
oAuthUriBuilder.queryParameters['state'] = instanceUrl.toString();
|
||||
web.window.location.href = oAuthUriBuilder.toString();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/libphylum/responses/responses.dart';
|
||||
import 'package:phylum/ui/app/router.dart';
|
||||
import 'package:phylum/ui/app/routes.dart';
|
||||
import 'package:phylum/ui/login/instance_config.dart';
|
||||
import 'package:phylum/ui/login/request.dart';
|
||||
import 'package:phylum/util/dialogs.dart';
|
||||
import 'package:phylum/util/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uri/uri.dart';
|
||||
|
||||
import 'oauth_stub.dart' if (dart.library.js_interop) 'oauth_web.dart' if (dart.library.ffi) 'oauth_vm.dart';
|
||||
|
||||
class PasswordLoginFragment extends StatefulWidget {
|
||||
final InstanceConfig instanceConfig;
|
||||
final Function()? onBackPressed;
|
||||
final Uri instanceUrl;
|
||||
final Function(String, String)? requestLogin;
|
||||
final Function(String)? requestPasswordReset;
|
||||
final Function(String, String)? resetPassword;
|
||||
final List<OpenIDProvider> openIDProviders;
|
||||
|
||||
const PasswordLoginFragment({
|
||||
super.key,
|
||||
required this.instanceConfig,
|
||||
required this.onBackPressed,
|
||||
required this.instanceUrl,
|
||||
required this.requestLogin,
|
||||
required this.requestPasswordReset,
|
||||
required this.resetPassword,
|
||||
required this.openIDProviders,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -38,7 +41,11 @@ class _PasswordLoginFragmentState extends State<PasswordLoginFragment> {
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
if (widget.requestLogin != null)
|
||||
ListTile(
|
||||
title: Text(widget.instanceConfig.url.toString()),
|
||||
trailing: IconButton(onPressed: widget.onBackPressed, icon: Icon(Icons.edit)),
|
||||
),
|
||||
if (widget.instanceConfig.hasEmailLogin)
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Email'),
|
||||
@@ -47,7 +54,7 @@ class _PasswordLoginFragmentState extends State<PasswordLoginFragment> {
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onChanged: (value) => setState(() => _email = value.trim()),
|
||||
),
|
||||
if (widget.requestLogin != null)
|
||||
if (widget.instanceConfig.password)
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
label: Text('Password'),
|
||||
@@ -56,9 +63,17 @@ class _PasswordLoginFragmentState extends State<PasswordLoginFragment> {
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
onChanged: (value) => setState(() => _password = value),
|
||||
onSubmitted: _email.isEmpty ? null : (value) => widget.requestLogin!(_email, _password),
|
||||
onSubmitted: _email.isEmpty
|
||||
? null
|
||||
: (value) => _performLogin(context, widget.instanceConfig.url, _email, _password),
|
||||
),
|
||||
if (widget.requestPasswordReset != null)
|
||||
ElevatedButton(
|
||||
onPressed: _email.isEmpty || _password.isEmpty
|
||||
? null
|
||||
: () => _performLogin(context, widget.instanceConfig.url, _email, _password),
|
||||
child: Text('Login'),
|
||||
),
|
||||
if (widget.instanceConfig.passwordReset)
|
||||
TextButton(
|
||||
child: Text('Reset Password', style: TextStyle(color: Theme.of(context).colorScheme.primary)),
|
||||
onPressed: () async {
|
||||
@@ -84,7 +99,8 @@ class _PasswordLoginFragmentState extends State<PasswordLoginFragment> {
|
||||
await showInputDialog(context, labelText: 'Reset Token', barrierDismissable: true);
|
||||
if (token == null || !context.mounted) return;
|
||||
Navigator.of(context).pop(false);
|
||||
widget.resetPassword!(_email, token);
|
||||
context.read<PhylumRouterDelegate>().go(ResetPasswordRoute(
|
||||
instanceUri: widget.instanceConfig.url, email: _email, token: token));
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -101,43 +117,75 @@ class _PasswordLoginFragmentState extends State<PasswordLoginFragment> {
|
||||
) ??
|
||||
false;
|
||||
if (!confirm || !context.mounted) return;
|
||||
widget.requestPasswordReset!(_email);
|
||||
_requestPasswordReset(context, widget.instanceConfig.url, _email);
|
||||
},
|
||||
),
|
||||
...widget.openIDProviders.map((p) => ElevatedButton(
|
||||
...widget.instanceConfig.openIDProviers.map((p) => ElevatedButton(
|
||||
onPressed: () async {
|
||||
final builder = UriBuilder.fromUri(Uri.parse(p.endpoint));
|
||||
final redirectBuilder = UriBuilder.fromUri(widget.instanceUrl);
|
||||
redirectBuilder.path = '/login/oauth';
|
||||
builder.queryParameters['client_id'] = p.clientID;
|
||||
builder.queryParameters['response_type'] = 'code';
|
||||
builder.queryParameters['redirect_uri'] = redirectBuilder.toString();
|
||||
builder.queryParameters['scope'] = 'openid email profile';
|
||||
startOAuthFlow(builder.build());
|
||||
startOAuthFlow(builder, widget.instanceConfig.url);
|
||||
},
|
||||
child: Text('Log In with ${p.name}'))),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
if (widget.onBackPressed != null)
|
||||
TextButton(
|
||||
onPressed: widget.onBackPressed,
|
||||
child: Text(
|
||||
'Back',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
if (widget.requestLogin != null)
|
||||
ElevatedButton(
|
||||
onPressed: _email.isEmpty || _password.isEmpty ? null : () => widget.requestLogin!(_email, _password),
|
||||
child: Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _performLogin(BuildContext context, Uri instanceUri, String email, String password) async {
|
||||
final builder = UriBuilder.fromUri(instanceUri);
|
||||
builder.path = '${builder.path}/api/v1/auth/password';
|
||||
final request = MultipartRequest('post', builder.build());
|
||||
request.fields['email'] = email;
|
||||
request.fields['password'] = password;
|
||||
|
||||
final responseString = await sendRequest(context, 'Logging In', request);
|
||||
if (responseString == null) return;
|
||||
if (context.mounted) {
|
||||
final accountManager = context.read<AccountManager<PhylumAccount>>();
|
||||
final navigator = Navigator.of(context);
|
||||
showProgressDialog(context, barrierDismissible: false, message: 'Logging In');
|
||||
try {
|
||||
final response =
|
||||
BootstrapLoginResponse.fromResponse((jsonDecode(responseString) as Map).cast<String, dynamic>());
|
||||
final account = await PhylumAccount.create(
|
||||
serverUrl: instanceUri,
|
||||
accessToken: response.accessToken!,
|
||||
user: response.user,
|
||||
);
|
||||
await response.process(account);
|
||||
await accountManager.addAccount(account);
|
||||
navigator.pop();
|
||||
} catch (e, stack) {
|
||||
navigator.pop();
|
||||
if (context.mounted) {
|
||||
showAlertDialog(
|
||||
context,
|
||||
barrierDismissible: false,
|
||||
title: 'Error',
|
||||
message: e.toString(),
|
||||
);
|
||||
}
|
||||
logger.w('Login Error', error: e, stackTrace: stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _requestPasswordReset(BuildContext context, Uri instanceUrl, String email) async {
|
||||
final builder = UriBuilder.fromUri(instanceUrl);
|
||||
builder.path = '${builder.path}/api/v1/auth/request-password-reset';
|
||||
final request = MultipartRequest('post', builder.build());
|
||||
request.fields['email'] = email;
|
||||
|
||||
final responseString = await sendRequest(context, 'Requesting Password Reset', request);
|
||||
if (responseString == null) return;
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
title: 'Password Reset Requested',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
Uri get webAppBaseUri => Uri();
|
||||
Uri? get webAppBaseUri => null;
|
||||
|
||||
Reference in New Issue
Block a user