Add change password flow for anonymous users

This commit is contained in:
Robert Long 2021-12-16 16:43:35 -08:00
parent d7fd06a250
commit a7539eb34e
5 changed files with 181 additions and 45 deletions

View File

@ -130,8 +130,14 @@ export function ClientProvider({ homeserverUrl, children }) {
const authStore = localStorage.getItem("matrix-auth-store");
if (authStore) {
const { user_id, device_id, access_token, guest, passwordlessUser } =
JSON.parse(authStore);
const {
user_id,
device_id,
access_token,
guest,
passwordlessUser,
tempPassword,
} = JSON.parse(authStore);
const client = await initClient(
{
@ -151,6 +157,7 @@ export function ClientProvider({ homeserverUrl, children }) {
access_token,
guest,
passwordlessUser,
tempPassword,
})
);
@ -311,10 +318,13 @@ export function ClientProvider({ homeserverUrl, children }) {
deviceId: device_id,
});
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token, passwordlessUser })
);
const session = { user_id, device_id, access_token, passwordlessUser };
if (passwordlessUser) {
session.tempPassword = password;
}
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
setState({
client,
@ -340,6 +350,45 @@ export function ClientProvider({ homeserverUrl, children }) {
}
}, []);
const changePassword = useCallback(
async (password) => {
const { tempPassword, passwordlessUser, ...existingSession } = JSON.parse(
localStorage.getItem("matrix-auth-store")
);
await client.setPassword(
{
type: "m.login.password",
identifier: {
type: "m.id.user",
user: existingSession.user_id,
},
user: existingSession.user_id,
password: tempPassword,
},
password
);
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({
...existingSession,
passwordlessUser: false,
})
);
setState({
client,
loading: false,
isGuest: false,
isAuthenticated: true,
isPasswordlessUser: false,
userName: client.getUserIdLocalpart(),
});
},
[client]
);
const logout = useCallback(() => {
localStorage.removeItem("matrix-auth-store");
window.location = "/";
@ -355,6 +404,7 @@ export function ClientProvider({ homeserverUrl, children }) {
login,
registerGuest,
register,
changePassword,
logout,
userName,
}),
@ -367,6 +417,7 @@ export function ClientProvider({ homeserverUrl, children }) {
login,
registerGuest,
register,
changePassword,
logout,
userName,
]

View File

@ -22,17 +22,30 @@ export function Field({ children, className, ...rest }) {
}
export const InputField = forwardRef(
({ id, label, className, type, checked, prefix, suffix, ...rest }, ref) => {
(
{ id, label, className, type, checked, prefix, suffix, disabled, ...rest },
ref
) => {
return (
<Field
className={classNames(
type === "checkbox" ? styles.checkboxField : styles.inputField,
{ [styles.prefix]: !!prefix },
{
[styles.prefix]: !!prefix,
[styles.disabled]: disabled,
},
className
)}
>
{prefix && <span>{prefix}</span>}
<input id={id} {...rest} ref={ref} type={type} checked={checked} />
<input
id={id}
{...rest}
ref={ref}
type={type}
checked={checked}
disabled={disabled}
/>
<label htmlFor={id}>
{type === "checkbox" && (
<div className={styles.checkbox}>

View File

@ -33,17 +33,26 @@
font-size: 15px;
border: none;
border-radius: 4px;
padding: 11px 9px;
padding: 12px 9px 10px 9px;
color: var(--textColor1);
background-color: var(--bgColor1);
flex: 1;
min-width: 0;
}
.inputField.disabled input,
.inputField.disabled span {
color: var(--textColor2);
}
.inputField span {
padding: 11px 9px;
}
.inputField span:first-child {
padding-right: 0;
}
.inputField input::placeholder {
transition: color 0.25s ease-in 0s;
color: transparent;

View File

@ -21,14 +21,21 @@ import { Button } from "./button";
import { useClient } from "./ConferenceCallManagerHooks";
import styles from "./LoginPage.module.css";
import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
import { LoadingView } from "./FullScreenView";
export function RegisterPage() {
const { register } = useClient();
const registerUsernameRef = useRef();
const {
loading,
client,
register,
changePassword,
isAuthenticated,
isPasswordlessUser,
} = useClient();
const confirmPasswordRef = useRef();
const history = useHistory();
const location = useLocation();
const [loading, setLoading] = useState(false);
const [registering, setRegistering] = useState(false);
const [error, setError] = useState();
const [password, setPassword] = useState("");
const [passwordConfirmation, setPasswordConfirmation] = useState("");
@ -36,27 +43,55 @@ export function RegisterPage() {
const onSubmitRegisterForm = useCallback(
(e) => {
e.preventDefault();
setLoading(true);
register(
registerUsernameRef.current.value,
registerPasswordRef.current.value
)
.then(() => {
if (location.state && location.state.from) {
history.push(location.state.from);
} else {
history.push("/");
}
})
.catch((error) => {
setError(error);
setLoading(false);
});
const data = new FormData(e.target);
const userName = data.get("userName");
const password = data.get("password");
const passwordConfirmation = data.get("passwordConfirmation");
if (password !== passwordConfirmation) {
return;
}
setRegistering(true);
console.log(isPasswordlessUser);
if (isPasswordlessUser) {
changePassword(password)
.then(() => {
if (location.state && location.state.from) {
history.push(location.state.from);
} else {
history.push("/");
}
})
.catch((error) => {
setError(error);
setRegistering(false);
});
} else {
register(userName, password)
.then(() => {
if (location.state && location.state.from) {
history.push(location.state.from);
} else {
history.push("/");
}
})
.catch((error) => {
setError(error);
setRegistering(false);
});
}
},
[register, location, history]
[register, changePassword, location, history, isPasswordlessUser]
);
useEffect(() => {
if (!confirmPasswordRef.current) {
return;
}
if (password && passwordConfirmation && password !== passwordConfirmation) {
confirmPasswordRef.current.setCustomValidity("Passwords must match");
} else {
@ -64,6 +99,16 @@ export function RegisterPage() {
}
}, [password, passwordConfirmation]);
useEffect(() => {
if (!loading && isAuthenticated && !isPasswordlessUser) {
history.push("/");
}
}, [history, isAuthenticated, isPasswordlessUser]);
if (loading) {
return <LoadingView />;
}
return (
<>
<div className={styles.container}>
@ -75,18 +120,26 @@ export function RegisterPage() {
<FieldRow>
<InputField
type="text"
ref={registerUsernameRef}
name="userName"
placeholder="Username"
label="Username"
autoCorrect="off"
autoCapitalize="none"
prefix="@"
suffix={`:${window.location.host}`}
value={
isAuthenticated && isPasswordlessUser
? client.getUserIdLocalpart()
: undefined
}
onChange={(e) => setUserName(e.target.value)}
disabled={isAuthenticated && isPasswordlessUser}
/>
</FieldRow>
<FieldRow>
<InputField
required
name="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
@ -98,6 +151,7 @@ export function RegisterPage() {
<InputField
required
type="password"
name="passwordConfirmation"
onChange={(e) => setPasswordConfirmation(e.target.value)}
value={passwordConfirmation}
placeholder="Confirm Password"
@ -111,8 +165,8 @@ export function RegisterPage() {
</FieldRow>
)}
<FieldRow>
<Button type="submit" disabled={loading}>
{loading ? "Registering..." : "Register"}
<Button type="submit" disabled={registering}>
{registering ? "Registering..." : "Register"}
</Button>
</FieldRow>
</form>

View File

@ -60,8 +60,15 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
export function Room() {
const [registeringGuest, setRegisteringGuest] = useState(false);
const [registrationError, setRegistrationError] = useState();
const { loading, isAuthenticated, error, client, registerGuest, isGuest } =
useClient();
const {
loading,
isAuthenticated,
error,
client,
registerGuest,
isGuest,
isPasswordlessUser,
} = useClient();
useEffect(() => {
if (!loading && !isAuthenticated) {
@ -86,10 +93,16 @@ export function Room() {
return <ErrorView error={registrationError || error} />;
}
return <GroupCall client={client} isGuest={isGuest} />;
return (
<GroupCall
client={client}
isGuest={isGuest}
isPasswordlessUser={isPasswordlessUser}
/>
);
}
export function GroupCall({ client, isGuest }) {
export function GroupCall({ client, isGuest, isPasswordlessUser }) {
const { roomId: maybeRoomId } = useParams();
const { hash, search } = useLocation();
const [simpleGrid, viaServers] = useMemo(() => {
@ -118,6 +131,7 @@ export function GroupCall({ client, isGuest }) {
return (
<GroupCallView
isGuest={isGuest}
isPasswordlessUser={isPasswordlessUser}
client={client}
roomId={roomId}
groupCall={groupCall}
@ -129,6 +143,7 @@ export function GroupCall({ client, isGuest }) {
export function GroupCallView({
client,
isGuest,
isPasswordlessUser,
roomId,
groupCall,
simpleGrid,
@ -184,7 +199,7 @@ export function GroupCallView({
const onLeave = useCallback(() => {
leave();
if (!isGuest) {
if (!isGuest && !isPasswordlessUser) {
history.push("/");
} else {
setLeft(true);
@ -494,18 +509,12 @@ export function CallEndedScreen() {
<ul>
<li>Easily access all your previous call links</li>
<li>Set a username and avatar</li>
<li>
Get your own Matrix ID so you can log in to{" "}
<a href="https://element.io" target="_blank">
Element
</a>
</li>
</ul>
<LinkButton
className={styles.callEndedButton}
size="lg"
variant="default"
to="/login"
to="/register"
>
Create account
</LinkButton>