support registration via pars tokens

Daniel Gultsch created

Change summary

src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java | 208 
src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java      |  12 
src/conversations/res/layout/magic_create.xml                             | 173 
src/conversations/res/values/strings.xml                                  |   2 
src/main/java/eu/siacs/conversations/entities/Account.java                |   4 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java          |  13 
src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java           |  14 
src/main/java/eu/siacs/conversations/utils/XmppUri.java                   |   5 
src/main/java/eu/siacs/conversations/xml/Namespace.java                   |   2 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java             |  27 
src/main/res/values/strings.xml                                           |   1 
11 files changed, 288 insertions(+), 173 deletions(-)

Detailed changes

src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java ๐Ÿ”—

@@ -2,7 +2,9 @@ package eu.siacs.conversations.ui;
 
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.databinding.DataBindingUtil;
 import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.View;
@@ -15,102 +17,124 @@ import java.security.SecureRandom;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.MagicCreateBinding;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.utils.CryptoHelper;
 import rocks.xmpp.addr.Jid;
 
 public class MagicCreateActivity extends XmppActivity implements TextWatcher {
 
-	private TextView mFullJidDisplay;
-	private EditText mUsername;
-
-	@Override
-	protected void refreshUiReal() {
-
-	}
-
-	@Override
-	void onBackendConnected() {
-
-	}
-
-	@Override
-	public void onStart() {
-		super.onStart();
-		final int theme = findTheme();
-		if (this.mTheme != theme) {
-			recreate();
-		}
-	}
-
-	@Override
-	protected void onCreate(final Bundle savedInstanceState) {
-		if (getResources().getBoolean(R.bool.portrait_only)) {
-			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-		}
-		super.onCreate(savedInstanceState);
-		setContentView(R.layout.magic_create);
-		setSupportActionBar(findViewById(R.id.toolbar));
-		configureActionBar(getSupportActionBar());
-		mFullJidDisplay = findViewById(R.id.full_jid);
-		mUsername = findViewById(R.id.username);
-		Button next = findViewById(R.id.create_account);
-		next.setOnClickListener(v -> {
-			try {
-				String username = mUsername.getText().toString();
-				Jid jid = Jid.of(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
-				if (!jid.getEscapedLocal().equals(jid.getLocal())|| username.length() < 3) {
-					mUsername.setError(getString(R.string.invalid_username));
-					mUsername.requestFocus();
-				} else {
-					mUsername.setError(null);
-					Account account = xmppConnectionService.findAccountByJid(jid);
-					if (account == null) {
-						account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
-						account.setOption(Account.OPTION_REGISTER, true);
-						account.setOption(Account.OPTION_DISABLED, true);
-						account.setOption(Account.OPTION_MAGIC_CREATE, true);
-						xmppConnectionService.createAccount(account);
-					}
-					Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
-					intent.putExtra("jid", account.getJid().asBareJid().toString());
-					intent.putExtra("init", true);
-					intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-					Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
-					StartConversationActivity.addInviteUri(intent, getIntent());
-					startActivity(intent);
-				}
-			} catch (IllegalArgumentException e) {
-				mUsername.setError(getString(R.string.invalid_username));
-				mUsername.requestFocus();
-			}
-		});
-		mUsername.addTextChangedListener(this);
-	}
-
-	@Override
-	public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-
-	}
-
-	@Override
-	public void onTextChanged(CharSequence s, int start, int before, int count) {
-
-	}
-
-	@Override
-	public void afterTextChanged(Editable s) {
-		if (s.toString().trim().length() > 0) {
-			try {
-				mFullJidDisplay.setVisibility(View.VISIBLE);
-				Jid jid = Jid.of(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
-				mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
-			} catch (IllegalArgumentException e) {
-				mFullJidDisplay.setVisibility(View.INVISIBLE);
-			}
-
-		} else {
-			mFullJidDisplay.setVisibility(View.INVISIBLE);
-		}
-	}
+    public static final String EXTRA_DOMAIN = "domain";
+    public static final String EXTRA_PRE_AUTH = "pre_auth";
+
+    private MagicCreateBinding binding;
+    private String domain;
+    private String preAuth;
+
+    @Override
+    protected void refreshUiReal() {
+
+    }
+
+    @Override
+    void onBackendConnected() {
+
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        final int theme = findTheme();
+        if (this.mTheme != theme) {
+            recreate();
+        }
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        final Intent data = getIntent();
+        this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN);
+        this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH);
+        if (getResources().getBoolean(R.bool.portrait_only)) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+        super.onCreate(savedInstanceState);
+        this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create);
+        setSupportActionBar((Toolbar) this.binding.toolbar);
+        configureActionBar(getSupportActionBar(), this.domain == null);
+        if (domain != null) {
+            binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain));
+            binding.finePrint.setVisibility(View.INVISIBLE);
+        }
+        binding.createAccount.setOnClickListener(v -> {
+            try {
+                final String username = binding.username.getText().toString();
+                final Jid jid;
+                if (this.domain == null) {
+                    jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN);
+                } else {
+                    jid = Jid.ofLocalAndDomain(username, this.domain);
+                }
+                if (!jid.getEscapedLocal().equals(jid.getLocal()) || username.length() < 3) {
+                    binding.username.setError(getString(R.string.invalid_username));
+                    binding.username.requestFocus();
+                } else {
+                    binding.username.setError(null);
+                    Account account = xmppConnectionService.findAccountByJid(jid);
+                    if (account == null) {
+                        account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
+                        account.setOption(Account.OPTION_REGISTER, true);
+                        account.setOption(Account.OPTION_DISABLED, true);
+                        account.setOption(Account.OPTION_MAGIC_CREATE, true);
+                        if (this.preAuth != null) {
+                            account.setKey(Account.PRE_AUTH_REGISTRATION_TOKEN, this.preAuth);
+                        }
+                        xmppConnectionService.createAccount(account);
+                    }
+                    Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
+                    intent.putExtra("jid", account.getJid().asBareJid().toString());
+                    intent.putExtra("init", true);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                    Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
+                    StartConversationActivity.addInviteUri(intent, getIntent());
+                    startActivity(intent);
+                }
+            } catch (IllegalArgumentException e) {
+                binding.username.setError(getString(R.string.invalid_username));
+                binding.username.requestFocus();
+            }
+        });
+        binding.username.addTextChangedListener(this);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        if (s.toString().trim().length() > 0) {
+            try {
+                binding.fullJid.setVisibility(View.VISIBLE);
+                final Jid jid;
+                if (this.domain == null) {
+                    jid = Jid.ofLocalAndDomain(s.toString(), Config.MAGIC_CREATE_DOMAIN);
+                } else {
+                    jid = Jid.ofLocalAndDomain(s.toString(), this.domain);
+                }
+                binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
+            } catch (IllegalArgumentException e) {
+                binding.fullJid.setVisibility(View.INVISIBLE);
+            }
+
+        } else {
+            binding.fullJid.setVisibility(View.INVISIBLE);
+        }
+    }
 }

src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java ๐Ÿ”—

@@ -8,6 +8,7 @@ import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.ConversationsActivity;
 import eu.siacs.conversations.ui.EditAccountActivity;
+import eu.siacs.conversations.ui.MagicCreateActivity;
 import eu.siacs.conversations.ui.ManageAccountActivity;
 import eu.siacs.conversations.ui.PickServerActivity;
 import eu.siacs.conversations.ui.StartConversationActivity;
@@ -15,6 +16,17 @@ import eu.siacs.conversations.ui.WelcomeActivity;
 
 public class SignupUtils {
 
+    public static boolean isSupportTokenRegistry() {
+        return true;
+    }
+
+    public static Intent getTokenRegistrationIntent(final Activity activity, String domain, String preauth) {
+        final Intent intent = new Intent(activity, MagicCreateActivity.class);
+        intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, domain);
+        intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preauth);
+        return intent;
+    }
+
     public static Intent getSignUpIntent(final Activity activity) {
         return getSignUpIntent(activity, false);
     }

src/conversations/res/layout/magic_create.xml ๐Ÿ”—

@@ -1,97 +1,112 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <include layout="@layout/toolbar" />
+    <LinearLayout
 
-    <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:fillViewport="true">
-        <RelativeLayout
+        android:orientation="vertical">
+
+        <include layout="@layout/toolbar" android:id="@+id/toolbar"/>
+
+        <ScrollView
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:background="?attr/color_background_primary">
+            android:fillViewport="true">
 
-            <LinearLayout
-                android:id="@+id/linearLayout"
+            <RelativeLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_alignParentBottom="true"
-                android:layout_alignParentLeft="true"
-                android:layout_alignParentStart="true"
-                android:minHeight="256dp"
-                android:orientation="vertical"
-                android:paddingBottom="10dp"
-                android:paddingLeft="16dp"
-                android:paddingRight="16dp">
-                <Space
+                android:background="?attr/color_background_primary">
+
+                <LinearLayout
+                    android:id="@+id/linearLayout"
                     android:layout_width="match_parent"
-                    android:layout_height="0dp"
-                    android:layout_weight="1"/>
-                <TextView
-                    android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/pick_your_username"
-                    android:textAppearance="@style/TextAppearance.Conversations.Title"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="8dp"
-                    android:text="@string/magic_create_text"
-                    android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
-                <EditText
-                    android:id="@+id/username"
+                    android:layout_alignParentStart="true"
+                    android:layout_alignParentLeft="true"
+                    android:layout_alignParentBottom="true"
+                    android:minHeight="256dp"
+                    android:orientation="vertical"
+                    android:paddingLeft="16dp"
+                    android:paddingRight="16dp"
+                    android:paddingBottom="10dp">
+
+                    <Space
+                        android:layout_width="match_parent"
+                        android:layout_height="0dp"
+                        android:layout_weight="1" />
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/pick_your_username"
+                        android:textAppearance="@style/TextAppearance.Conversations.Title" />
+
+                    <TextView
+                        android:id="@+id/instructions"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="8dp"
+                        android:text="@string/magic_create_text"
+                        android:textAppearance="@style/TextAppearance.Conversations.Body1" />
+
+                    <EditText
+                        android:id="@+id/username"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center_horizontal"
+                        android:hint="@string/username_hint"
+                        android:inputType="textNoSuggestions" />
+
+                    <TextView
+                        android:id="@+id/full_jid"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="8dp"
+                        android:text="@string/your_full_jid_will_be"
+                        android:textAppearance="@style/TextAppearance.Conversations.Caption"
+                        android:visibility="invisible" />
+
+                    <Button
+                        android:id="@+id/create_account"
+                        style="@style/Widget.Conversations.Button.Borderless"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="right"
+                        android:text="@string/next"
+                        android:textColor="?colorAccent" />
+                </LinearLayout>
+
+                <RelativeLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:hint="@string/username_hint"
-                    android:inputType="textNoSuggestions"/>
+                    android:layout_height="match_parent"
+                    android:layout_above="@+id/linearLayout"
+                    android:layout_alignParentStart="true"
+                    android:layout_alignParentLeft="true">
+
+                    <ImageView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_centerHorizontal="true"
+                        android:layout_centerVertical="true"
+                        android:padding="8dp"
+                        android:src="@drawable/main_logo" />
+                </RelativeLayout>
+
                 <TextView
-                    android:id="@+id/full_jid"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="8dp"
-                    android:text="@string/your_full_jid_will_be"
-                    android:textAppearance="@style/TextAppearance.Conversations.Caption"
-                    android:visibility="invisible"/>
-                <Button
-                    android:id="@+id/create_account"
-                    style="@style/Widget.Conversations.Button.Borderless"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="right"
-                    android:text="@string/next"
-                    android:textColor="?colorAccent"/>
-            </LinearLayout>
-            <RelativeLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_above="@+id/linearLayout"
-                android:layout_alignParentLeft="true"
-                android:layout_alignParentStart="true">
-                <ImageView
+                    android:id="@+id/fine_print"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
                     android:layout_centerHorizontal="true"
-                    android:layout_centerVertical="true"
-                    android:padding="8dp"
-                    android:src="@drawable/main_logo"/>
+                    android:maxLines="1"
+                    android:paddingLeft="8dp"
+                    android:paddingRight="8dp"
+                    android:text="@string/free_for_six_month"
+                    android:textColor="?android:textColorSecondary"
+                    android:textSize="@dimen/fineprint_size" />
             </RelativeLayout>
-            <TextView
-                android:paddingLeft="8dp"
-                android:paddingRight="8dp"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentBottom="true"
-                android:textColor="?android:textColorSecondary"
-                android:textSize="@dimen/fineprint_size"
-                android:maxLines="1"
-                android:text="@string/free_for_six_month"
-                android:layout_centerHorizontal="true"/>
-        </RelativeLayout>
-    </ScrollView>
-</LinearLayout>
+        </ScrollView>
+    </LinearLayout>
+</layout>

src/conversations/res/values/strings.xml ๐Ÿ”—

@@ -5,5 +5,5 @@
     <string name="create_new_account">Create new account</string>
     <string name="do_you_have_an_account">Do you already have an XMPP account? This might be the case if you are already using a different XMPP client or have used Conversations before. If not you can create a new XMPP account right now.\nHint: Some email providers also provide XMPP accounts.</string>
     <string name="server_select_text">XMPP is a provider independent instant messaging network. You can use this client with what ever XMPP server you choose.\nHowever for your convenience we made it easy to create an account on conversations.imยน; a provider specially suited for the use with Conversations.</string>
-
+    <string name="magic_create_text_on_x">You have been invited to %1$s. We will guide you through the process of creating an account.\nWhen picking %1$s as a provider you will be able to communicate with users of other providers by giving them your full XMPP address.</string>
 </resources>

src/main/java/eu/siacs/conversations/entities/Account.java ๐Ÿ”—

@@ -50,6 +50,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
     public static final String RESOURCE = "resource";
 
     public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
+    public static final String PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";
 
     public static final int OPTION_USETLS = 0;
     public static final int OPTION_DISABLED = 1;
@@ -619,6 +620,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
         REGISTRATION_CONFLICT(true, false),
         REGISTRATION_NOT_SUPPORTED(true, false),
         REGISTRATION_PLEASE_WAIT(true, false),
+        REGISTRATION_INVALID_TOKEN(true,false),
         REGISTRATION_PASSWORD_TOO_WEAK(true, false),
         TLS_ERROR,
         INCOMPATIBLE_SERVER,
@@ -683,6 +685,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
                     return R.string.account_status_regis_success;
                 case REGISTRATION_NOT_SUPPORTED:
                     return R.string.account_status_regis_not_sup;
+                case REGISTRATION_INVALID_TOKEN:
+                    return R.string.account_status_regis_invalid_token;
                 case TLS_ERROR:
                     return R.string.account_status_tls_error;
                 case INCOMPATIBLE_SERVER:

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java ๐Ÿ”—

@@ -403,6 +403,16 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
             xmppConnectionService.deleteAccount(mAccount);
         }
 
+        final boolean magicCreate = mAccount != null && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
+        final Jid jid = mAccount == null ? null : mAccount.getJid();
+
+        if (SignupUtils.isSupportTokenRegistry() && jid != null && magicCreate && !jid.getDomain().equals(Config.MAGIC_CREATE_DOMAIN)) {
+            final Intent intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), mAccount.getKey(Account.PRE_AUTH_REGISTRATION_TOKEN));
+            startActivity(intent);
+            return;
+        }
+
+
         if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
             Intent intent = SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister);
             startActivity(intent);
@@ -816,6 +826,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
             return false;
         }
         switch (item.getItemId()) {
+            case android.R.id.home:
+                deleteAccountAndReturnIfNecessary();
+                break;
             case R.id.action_show_block_list:
                 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
                 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());

src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java ๐Ÿ”—

@@ -9,12 +9,14 @@ import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
 import android.widget.Toast;
 
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.persistance.DatabaseBackend;
 import eu.siacs.conversations.utils.SignupUtils;
@@ -87,6 +89,18 @@ public class UriHandlerActivity extends AppCompatActivity {
         final XmppUri xmppUri = new XmppUri(uri);
         final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(true);
 
+        if (SignupUtils.isSupportTokenRegistry() && xmppUri.isJidValid() && xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
+            final String preauth = xmppUri.getParamater("preauth");
+            final Jid jid = xmppUri.getJid();
+            if (jid.isDomainJid()) {
+                intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preauth);
+                startActivity(intent);
+                return;
+            }
+            Log.d(Config.LOGTAG,"attempting to register on "+jid+" with preauth="+preauth);
+            return;
+        }
+
         if (accounts.size() == 0) {
             if (xmppUri.isJidValid()) {
                 intent = SignupUtils.getSignUpIntent(this);

src/main/java/eu/siacs/conversations/utils/XmppUri.java ๐Ÿ”—

@@ -28,6 +28,7 @@ public class XmppUri {
 
 	public static final String ACTION_JOIN = "join";
 	public static final String ACTION_MESSAGE = "message";
+	public static final String ACTION_REGISTER = "register";
 
 	public XmppUri(String uri) {
 		try {
@@ -194,6 +195,10 @@ public class XmppUri {
 		return parameters.get("name");
 	}
 
+	public String getParamater(String key) {
+		return this.parameters.get(key);
+	}
+
 	public List<Fingerprint> getFingerprints() {
 		return this.fingerprints;
 	}

src/main/java/eu/siacs/conversations/xml/Namespace.java ๐Ÿ”—

@@ -37,4 +37,6 @@ public final class Namespace {
 	public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
 	public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:0";
 	public static final String BOOKMARKS2_COMPAT = BOOKMARKS2+"#compat";
+	public static final String INVITE = "urn:xmpp:invite";
+	public static final String PARS = "urn:xmpp:pars:0";
 }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java ๐Ÿ”—

@@ -112,6 +112,7 @@ public class XmppConnection implements Runnable {
         public void onIqPacketReceived(Account account, IqPacket packet) {
             if (packet.getType() == IqPacket.TYPE.RESULT) {
                 account.setOption(Account.OPTION_REGISTER, false);
+                Log.d(Config.LOGTAG, account.getJid().asBareJid()+": successfully registered new account on server");
                 throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
             } else {
                 final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
@@ -838,7 +839,7 @@ public class XmppConnection implements Runnable {
             sendStartTLS();
         } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
             if (isSecure) {
-                sendRegistryRequest();
+                register();
             } else {
                 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to find STARTTLS for registration process "+ XmlHelper.printElementNames(this.streamFeatures));
                 throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
@@ -912,6 +913,26 @@ public class XmppConnection implements Runnable {
         return mechanisms;
     }
 
+
+    private void register() {
+        final String preAuth = account.getKey(Account.PRE_AUTH_REGISTRATION_TOKEN);
+        if (preAuth != null && features.invite()) {
+            final IqPacket preAuthRequest = new IqPacket(IqPacket.TYPE.SET);
+            preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
+            sendUnmodifiedIqPacket(preAuthRequest, (account, response) -> {
+                if (response.getType() == IqPacket.TYPE.RESULT) {
+                    sendRegistryRequest();
+                } else {
+                    final Element error = response.getError();
+                    Log.d(Config.LOGTAG,account.getJid().asBareJid()+": failed to pre auth. "+error);
+                    throw new StateChangingError(Account.State.REGISTRATION_INVALID_TOKEN);
+                }
+            }, true);
+        } else {
+            sendRegistryRequest();
+        }
+    }
+
     private void sendRegistryRequest() {
         final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
         register.query(Namespace.REGISTER);
@@ -1776,6 +1797,10 @@ public class XmppConnection implements Runnable {
             return hasDiscoFeature(Jid.of(account.getServer()), Namespace.REGISTER);
         }
 
+        public boolean invite() {
+            return connection.streamFeatures != null && connection.streamFeatures.hasChild("register", Namespace.INVITE);
+        }
+
         public boolean sm() {
             return streamId != null
                     || (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));

src/main/res/values/strings.xml ๐Ÿ”—

@@ -155,6 +155,7 @@
     <string name="account_status_regis_conflict">Username already in use</string>
     <string name="account_status_regis_success">Registration completed</string>
     <string name="account_status_regis_not_sup">Server does not support registration</string>
+    <string name="account_status_regis_invalid_token">Invalid registration token</string>
     <string name="account_status_tls_error">TLS negotiation failed</string>
     <string name="account_status_policy_violation">Policy violation</string>
     <string name="account_status_incompatible_server">Incompatible server</string>