diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index d85aeb698..0b41c0771 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -255,6 +255,7 @@
"or_separator": "Or",
"password_confirm_label": "Confirm password",
"password_label": "Password",
+ "passwordless_notice": "This account doesn't need a password. We'll send a verification code to your email address instead.",
"terms_of_service": "I agree to the Terms and Conditions",
"token_checking": "Checking invite codeā¦",
"token_invalid": "This invite code is invalid or has expired",
diff --git a/frontend/src/entrypoints/register.tsx b/frontend/src/entrypoints/register.tsx
index 41933b95c..68c841a07 100644
--- a/frontend/src/entrypoints/register.tsx
+++ b/frontend/src/entrypoints/register.tsx
@@ -4,7 +4,7 @@
// Please see LICENSE files in the repository root for full details.
import { QueryClient, useQuery } from "@tanstack/react-query";
-import { Form, InlineSpinner } from "@vector-im/compound-web";
+import { Form, InlineSpinner, Text } from "@vector-im/compound-web";
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import * as v from "valibot";
@@ -129,6 +129,7 @@ const REGISTRATION_TOKEN_QUERY = graphql(`
valid
username
email
+ passwordless
}
}
`);
@@ -138,6 +139,7 @@ type TokenInfo = {
valid: boolean;
username?: string | null;
email?: string | null;
+ passwordless: boolean;
};
/** The settled result of the token lookup, rendered in a live region. */
@@ -606,8 +608,16 @@ const PasswordRegisterForm: React.FC<{ data: Data }> = ({ data }) => {
const forcedUsername = tokenInfo?.valid ? tokenInfo.username : undefined;
const forcedEmail = tokenInfo?.valid ? tokenInfo.email : undefined;
+ // A passwordless token skips password creation entirely; the email address is
+ // used to send a verification code instead
+ const passwordless = tokenInfo?.valid === true && tokenInfo.passwordless;
+
+ // Show the email field when the server requires one, when the token pins one,
+ // or when it is needed to send the passwordless verification code
const showEmail =
- data.features.password_registration_email_required || !!forcedEmail;
+ data.features.password_registration_email_required ||
+ !!forcedEmail ||
+ passwordless;
// With no provider to pick from there is nothing to choose, so the whole form
// is shown at once
@@ -794,11 +804,19 @@ const PasswordRegisterForm: React.FC<{ data: Data }> = ({ data }) => {
)}
-