feat: Implement registration with email

This commit is contained in:
Christian Pauly 2021-05-13 12:18:50 +02:00
parent 48bf1169bc
commit 19616f3457
5 changed files with 177 additions and 50 deletions

View File

@ -1357,6 +1357,16 @@
"fileName": {} "fileName": {}
} }
}, },
"invalidEmail": "Invalid email",
"@invalidEmail": {
"type": "text",
"placeholders": {}
},
"optionalAddEmail": "(Optional) Your email address",
"@optionalAddEmail": {
"type": "text",
"placeholders": {}
},
"pleaseChooseAUsername": "Please choose a username", "pleaseChooseAUsername": "Please choose a username",
"@pleaseChooseAUsername": { "@pleaseChooseAUsername": {
"type": "text", "type": "text",

View File

@ -0,0 +1,7 @@
import 'dart:math';
const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
Random _rnd = Random();
String getClientSecret(int length) => String.fromCharCodes(Iterable.generate(
length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length))));

View File

@ -1,6 +1,9 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:adaptive_page_layout/adaptive_page_layout.dart'; import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:email_validator/email_validator.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/utils/get_client_secret.dart';
import 'package:fluffychat/views/ui/sign_up_password_ui.dart'; import 'package:fluffychat/views/ui/sign_up_password_ui.dart';
import 'package:fluffychat/views/widgets/matrix.dart'; import 'package:fluffychat/views/widgets/matrix.dart';
@ -19,7 +22,9 @@ class SignUpPassword extends StatefulWidget {
class SignUpPasswordController extends State<SignUpPassword> { class SignUpPasswordController extends State<SignUpPassword> {
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final TextEditingController emailController = TextEditingController();
String passwordError; String passwordError;
String emailError;
bool loading = false; bool loading = false;
bool showPassword = true; bool showPassword = true;
@ -30,7 +35,7 @@ class SignUpPasswordController extends State<SignUpPassword> {
if (passwordController.text.isEmpty) { if (passwordController.text.isEmpty) {
setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword); setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword);
} else { } else {
setState(() => passwordError = null); setState(() => passwordError = emailError = null);
} }
if (passwordController.text.isEmpty) { if (passwordController.text.isEmpty) {
@ -39,6 +44,31 @@ class SignUpPasswordController extends State<SignUpPassword> {
try { try {
setState(() => loading = true); setState(() => loading = true);
if (emailController.text.isNotEmpty) {
emailController.text = emailController.text.trim();
if (!EmailValidator.validate(emailController.text)) {
setState(() => emailError = L10n.of(context).invalidEmail);
return;
}
matrix.currentClientSecret = getClientSecret(30);
Logs().d('Request email token');
matrix.currentThreepidCreds = await matrix.client.requestEmailToken(
emailController.text,
matrix.currentClientSecret,
1,
);
if (OkCancelResult.ok !=
await showOkCancelAlertDialog(
context: context,
message: L10n.of(context).weSentYouAnEmail,
okLabel: L10n.of(context).confirm,
cancelLabel: L10n.of(context).cancel,
)) {
matrix.currentClientSecret = matrix.currentThreepidCreds = null;
setState(() => loading = false);
return;
}
}
final waitForLogin = matrix.client.onLoginStateChanged.stream.first; final waitForLogin = matrix.client.onLoginStateChanged.stream.first;
await matrix.client.uiaRequestBackground((auth) => matrix.client.register( await matrix.client.uiaRequestBackground((auth) => matrix.client.register(
username: widget.username, username: widget.username,
@ -46,13 +76,22 @@ class SignUpPasswordController extends State<SignUpPassword> {
initialDeviceDisplayName: PlatformInfos.clientName, initialDeviceDisplayName: PlatformInfos.clientName,
auth: auth, auth: auth,
)); ));
if (matrix.currentClientSecret != null &&
matrix.currentThreepidCreds != null) {
Logs().d('Add third party identifier');
await matrix.client.addThirdPartyIdentifier(
matrix.currentClientSecret,
matrix.currentThreepidCreds.sid,
);
}
await waitForLogin; await waitForLogin;
} catch (exception) { } catch (exception) {
setState(() => passwordError = exception.toString()); setState(() => emailError = exception.toString());
return setState(() => loading = false); return setState(() => loading = false);
} }
await matrix.client.onLoginStateChanged.stream await matrix.client.onLoginStateChanged.stream
.firstWhere((l) => l == LoginState.logged); .firstWhere((l) => l == LoginState.logged);
// tchncs.de
try { try {
await matrix.client await matrix.client
.setDisplayname(matrix.client.userID, widget.displayname); .setDisplayname(matrix.client.userID, widget.displayname);

View File

@ -47,6 +47,21 @@ class SignUpPasswordUI extends StatelessWidget {
labelText: L10n.of(context).password), labelText: L10n.of(context).password),
), ),
), ),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: controller.emailController,
readOnly: controller.loading,
autocorrect: false,
keyboardType: TextInputType.emailAddress,
onSubmitted: (_) => controller.signUpAction,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline_outlined),
errorText: controller.emailError,
hintText: 'email@example.com',
labelText: L10n.of(context).optionalAddEmail),
),
),
SizedBox(height: 12), SizedBox(height: 12),
Hero( Hero(
tag: 'loginButton', tag: 'loginButton',

View File

@ -121,7 +121,11 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
set cachedPassword(String p) => _cachedPassword = p; set cachedPassword(String p) => _cachedPassword = p;
String currentClientSecret;
RequestTokenResponse currentThreepidCreds;
void _onUiaRequest(UiaRequest uiaRequest) async { void _onUiaRequest(UiaRequest uiaRequest) async {
try {
if (uiaRequest.state != UiaRequestState.waitForUser || if (uiaRequest.state != UiaRequestState.waitForUser ||
uiaRequest.nextStages.isEmpty) return; uiaRequest.nextStages.isEmpty) return;
final stage = uiaRequest.nextStages.first; final stage = uiaRequest.nextStages.first;
@ -153,6 +157,30 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
identifier: AuthenticationUserIdentifier(user: client.userID), identifier: AuthenticationUserIdentifier(user: client.userID),
), ),
); );
case AuthenticationTypes.emailIdentity:
if (currentClientSecret == null || currentThreepidCreds == null) {
return uiaRequest
.cancel(Exception('This server requires an email address'));
}
final auth = AuthenticationThreePidCreds(
session: uiaRequest.session,
type: AuthenticationTypes.emailIdentity,
threepidCreds: [
ThreepidCreds(
sid: currentThreepidCreds.sid,
clientSecret: currentClientSecret,
),
],
);
currentThreepidCreds = currentClientSecret = null;
return uiaRequest.completeStage(auth);
case AuthenticationTypes.dummy:
return uiaRequest.completeStage(
AuthenticationData(
type: AuthenticationTypes.dummy,
session: uiaRequest.session,
),
);
default: default:
await launch( await launch(
client.homeserver.toString() + client.homeserver.toString() +
@ -173,6 +201,10 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
return uiaRequest.cancel(); return uiaRequest.cancel();
} }
} }
} catch (e, s) {
Logs().e('Error while background UIA', e, s);
return uiaRequest.cancel(e);
}
} }
bool webHasFocus = true; bool webHasFocus = true;
@ -422,3 +454,27 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
); );
} }
} }
class FixedThreepidCreds extends ThreepidCreds {
FixedThreepidCreds({
String sid,
String clientSecret,
String idServer,
String idAccessToken,
}) : super(
sid: sid,
clientSecret: clientSecret,
idServer: idServer,
idAccessToken: idAccessToken,
);
@override
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['sid'] = sid;
data['client_secret'] = clientSecret;
if (idServer != null) data['id_server'] = idServer;
if (idAccessToken != null) data['id_access_token'] = idAccessToken;
return data;
}
}