mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	feat: Implement registration with email
This commit is contained in:
		
							parent
							
								
									48bf1169bc
								
							
						
					
					
						commit
						19616f3457
					
				@ -1357,6 +1357,16 @@
 | 
			
		||||
      "fileName": {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "invalidEmail": "Invalid email",
 | 
			
		||||
  "@invalidEmail": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "optionalAddEmail": "(Optional) Your email address",
 | 
			
		||||
  "@optionalAddEmail": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "pleaseChooseAUsername": "Please choose a username",
 | 
			
		||||
  "@pleaseChooseAUsername": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								lib/utils/get_client_secret.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/utils/get_client_secret.dart
									
									
									
									
									
										Normal 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))));
 | 
			
		||||
@ -1,6 +1,9 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:email_validator/email_validator.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/widgets/matrix.dart';
 | 
			
		||||
@ -19,7 +22,9 @@ class SignUpPassword extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
  final TextEditingController passwordController = TextEditingController();
 | 
			
		||||
  final TextEditingController emailController = TextEditingController();
 | 
			
		||||
  String passwordError;
 | 
			
		||||
  String emailError;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
  bool showPassword = true;
 | 
			
		||||
 | 
			
		||||
@ -30,7 +35,7 @@ class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
    if (passwordController.text.isEmpty) {
 | 
			
		||||
      setState(() => passwordError = L10n.of(context).pleaseEnterYourPassword);
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() => passwordError = null);
 | 
			
		||||
      setState(() => passwordError = emailError = null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (passwordController.text.isEmpty) {
 | 
			
		||||
@ -39,6 +44,31 @@ class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      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;
 | 
			
		||||
      await matrix.client.uiaRequestBackground((auth) => matrix.client.register(
 | 
			
		||||
            username: widget.username,
 | 
			
		||||
@ -46,13 +76,22 @@ class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
            initialDeviceDisplayName: PlatformInfos.clientName,
 | 
			
		||||
            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;
 | 
			
		||||
    } catch (exception) {
 | 
			
		||||
      setState(() => passwordError = exception.toString());
 | 
			
		||||
      setState(() => emailError = exception.toString());
 | 
			
		||||
      return setState(() => loading = false);
 | 
			
		||||
    }
 | 
			
		||||
    await matrix.client.onLoginStateChanged.stream
 | 
			
		||||
        .firstWhere((l) => l == LoginState.logged);
 | 
			
		||||
    // tchncs.de
 | 
			
		||||
    try {
 | 
			
		||||
      await matrix.client
 | 
			
		||||
          .setDisplayname(matrix.client.userID, widget.displayname);
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,21 @@ class SignUpPasswordUI extends StatelessWidget {
 | 
			
		||||
                    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),
 | 
			
		||||
            Hero(
 | 
			
		||||
              tag: 'loginButton',
 | 
			
		||||
 | 
			
		||||
@ -121,57 +121,89 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
 | 
			
		||||
 | 
			
		||||
  set cachedPassword(String p) => _cachedPassword = p;
 | 
			
		||||
 | 
			
		||||
  String currentClientSecret;
 | 
			
		||||
  RequestTokenResponse currentThreepidCreds;
 | 
			
		||||
 | 
			
		||||
  void _onUiaRequest(UiaRequest uiaRequest) async {
 | 
			
		||||
    if (uiaRequest.state != UiaRequestState.waitForUser ||
 | 
			
		||||
        uiaRequest.nextStages.isEmpty) return;
 | 
			
		||||
    final stage = uiaRequest.nextStages.first;
 | 
			
		||||
    switch (stage) {
 | 
			
		||||
      case AuthenticationTypes.password:
 | 
			
		||||
        final input = cachedPassword ??
 | 
			
		||||
            (await showTextInputDialog(
 | 
			
		||||
              context: context,
 | 
			
		||||
              title: L10n.of(context).pleaseEnterYourPassword,
 | 
			
		||||
              okLabel: L10n.of(context).ok,
 | 
			
		||||
              cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
              useRootNavigator: false,
 | 
			
		||||
              textFields: [
 | 
			
		||||
                DialogTextField(
 | 
			
		||||
                  minLines: 1,
 | 
			
		||||
                  maxLines: 1,
 | 
			
		||||
                  obscureText: true,
 | 
			
		||||
                  hintText: '******',
 | 
			
		||||
                )
 | 
			
		||||
              ],
 | 
			
		||||
            ))
 | 
			
		||||
                ?.single;
 | 
			
		||||
        if (input?.isEmpty ?? true) return;
 | 
			
		||||
        return uiaRequest.completeStage(
 | 
			
		||||
          AuthenticationPassword(
 | 
			
		||||
            session: uiaRequest.session,
 | 
			
		||||
            user: client.userID,
 | 
			
		||||
            password: input,
 | 
			
		||||
            identifier: AuthenticationUserIdentifier(user: client.userID),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      default:
 | 
			
		||||
        await launch(
 | 
			
		||||
          client.homeserver.toString() +
 | 
			
		||||
              '/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}',
 | 
			
		||||
        );
 | 
			
		||||
        if (OkCancelResult.ok ==
 | 
			
		||||
            await showOkCancelAlertDialog(
 | 
			
		||||
              message: L10n.of(context).pleaseFollowInstructionsOnWeb,
 | 
			
		||||
              context: context,
 | 
			
		||||
              useRootNavigator: false,
 | 
			
		||||
              okLabel: L10n.of(context).next,
 | 
			
		||||
              cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
            )) {
 | 
			
		||||
    try {
 | 
			
		||||
      if (uiaRequest.state != UiaRequestState.waitForUser ||
 | 
			
		||||
          uiaRequest.nextStages.isEmpty) return;
 | 
			
		||||
      final stage = uiaRequest.nextStages.first;
 | 
			
		||||
      switch (stage) {
 | 
			
		||||
        case AuthenticationTypes.password:
 | 
			
		||||
          final input = cachedPassword ??
 | 
			
		||||
              (await showTextInputDialog(
 | 
			
		||||
                context: context,
 | 
			
		||||
                title: L10n.of(context).pleaseEnterYourPassword,
 | 
			
		||||
                okLabel: L10n.of(context).ok,
 | 
			
		||||
                cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
                useRootNavigator: false,
 | 
			
		||||
                textFields: [
 | 
			
		||||
                  DialogTextField(
 | 
			
		||||
                    minLines: 1,
 | 
			
		||||
                    maxLines: 1,
 | 
			
		||||
                    obscureText: true,
 | 
			
		||||
                    hintText: '******',
 | 
			
		||||
                  )
 | 
			
		||||
                ],
 | 
			
		||||
              ))
 | 
			
		||||
                  ?.single;
 | 
			
		||||
          if (input?.isEmpty ?? true) return;
 | 
			
		||||
          return uiaRequest.completeStage(
 | 
			
		||||
            AuthenticationData(session: uiaRequest.session),
 | 
			
		||||
            AuthenticationPassword(
 | 
			
		||||
              session: uiaRequest.session,
 | 
			
		||||
              user: client.userID,
 | 
			
		||||
              password: input,
 | 
			
		||||
              identifier: AuthenticationUserIdentifier(user: client.userID),
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          return uiaRequest.cancel();
 | 
			
		||||
        }
 | 
			
		||||
        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:
 | 
			
		||||
          await launch(
 | 
			
		||||
            client.homeserver.toString() +
 | 
			
		||||
                '/_matrix/client/r0/auth/$stage/fallback/web?session=${uiaRequest.session}',
 | 
			
		||||
          );
 | 
			
		||||
          if (OkCancelResult.ok ==
 | 
			
		||||
              await showOkCancelAlertDialog(
 | 
			
		||||
                message: L10n.of(context).pleaseFollowInstructionsOnWeb,
 | 
			
		||||
                context: context,
 | 
			
		||||
                useRootNavigator: false,
 | 
			
		||||
                okLabel: L10n.of(context).next,
 | 
			
		||||
                cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
              )) {
 | 
			
		||||
            return uiaRequest.completeStage(
 | 
			
		||||
              AuthenticationData(session: uiaRequest.session),
 | 
			
		||||
            );
 | 
			
		||||
          } else {
 | 
			
		||||
            return uiaRequest.cancel();
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      Logs().e('Error while background UIA', e, s);
 | 
			
		||||
      return uiaRequest.cancel(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user