mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Add privacy policy flow
This commit is contained in:
parent
493445a6b0
commit
66e5ec976b
@ -21,8 +21,9 @@ import React, {
|
||||
createContext,
|
||||
useMemo,
|
||||
useContext,
|
||||
useRef,
|
||||
} from "react";
|
||||
import matrix from "matrix-js-sdk/src/browser-index";
|
||||
import matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index";
|
||||
import {
|
||||
GroupCallIntent,
|
||||
GroupCallType,
|
||||
@ -385,6 +386,32 @@ export function ClientProvider({ children }) {
|
||||
[client]
|
||||
);
|
||||
|
||||
const setClient = useCallback((client, session) => {
|
||||
if (client) {
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
|
||||
setState({
|
||||
client,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: false,
|
||||
isGuest: false,
|
||||
userName: client.getUserIdLocalpart(),
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
isGuest: false,
|
||||
userName: null,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
window.location = "/";
|
||||
@ -403,6 +430,7 @@ export function ClientProvider({ children }) {
|
||||
changePassword,
|
||||
logout,
|
||||
userName,
|
||||
setClient,
|
||||
}),
|
||||
[
|
||||
loading,
|
||||
@ -416,6 +444,7 @@ export function ClientProvider({ children }) {
|
||||
changePassword,
|
||||
logout,
|
||||
userName,
|
||||
setClient,
|
||||
]
|
||||
);
|
||||
|
||||
@ -718,3 +747,104 @@ export function useProfile(client) {
|
||||
|
||||
return { loading, error, displayName, avatarUrl, saveProfile, success };
|
||||
}
|
||||
|
||||
export function useInteractiveLogin() {
|
||||
const { setClient } = useClient();
|
||||
const [state, setState] = useState({ loading: false });
|
||||
|
||||
const auth = useCallback(async (homeserver, username, password) => {
|
||||
const authClient = matrix.createClient(homeserver);
|
||||
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClient,
|
||||
busyChanged(loading) {
|
||||
setState((prev) => ({ ...prev, loading }));
|
||||
},
|
||||
async doRequest(auth, _background) {
|
||||
return authClient.login("m.login.password", {
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: username,
|
||||
},
|
||||
password,
|
||||
});
|
||||
},
|
||||
stateUpdated(nextStage, status) {
|
||||
console.log({ nextStage, status });
|
||||
},
|
||||
});
|
||||
|
||||
const { user_id, access_token, device_id } =
|
||||
await interactiveAuth.attemptAuth();
|
||||
|
||||
const client = await initClient({
|
||||
baseUrl: defaultHomeserver,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
});
|
||||
|
||||
setClient(client, { user_id, access_token, device_id });
|
||||
|
||||
return client;
|
||||
}, []);
|
||||
|
||||
return [state, auth];
|
||||
}
|
||||
|
||||
export function useInteractiveRegistration() {
|
||||
const { setClient } = useClient();
|
||||
const [state, setState] = useState({ privacyPolicyUrl: "#", loading: false });
|
||||
|
||||
const authClientRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
authClientRef.current = matrix.createClient(defaultHomeserver);
|
||||
|
||||
authClientRef.current.registerRequest({}).catch((error) => {
|
||||
const privacyPolicyUrl =
|
||||
error.data?.params["m.login.terms"]?.policies?.privacy_policy?.en?.url;
|
||||
|
||||
if (privacyPolicyUrl) {
|
||||
setState((prev) => ({ ...prev, privacyPolicyUrl }));
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const register = useCallback(async (username, password) => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClientRef.current,
|
||||
busyChanged(loading) {
|
||||
setState((prev) => ({ ...prev, loading }));
|
||||
},
|
||||
async doRequest(auth, _background) {
|
||||
return authClientRef.current.registerRequest({
|
||||
username,
|
||||
password,
|
||||
auth: auth || undefined,
|
||||
});
|
||||
},
|
||||
stateUpdated(nextStage, status) {
|
||||
if (nextStage === "m.login.terms") {
|
||||
interactiveAuth.submitAuthDict({ type: "m.login.terms" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { user_id, access_token, device_id } =
|
||||
await interactiveAuth.attemptAuth();
|
||||
|
||||
const client = await initClient({
|
||||
baseUrl: defaultHomeserver,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
});
|
||||
|
||||
setClient(client, { user_id, access_token, device_id });
|
||||
|
||||
return client;
|
||||
}, []);
|
||||
|
||||
return [state, register];
|
||||
}
|
||||
|
39
src/Home.jsx
39
src/Home.jsx
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from "react";
|
||||
import React, { useCallback, useState, useRef, useEffect } from "react";
|
||||
import { useHistory, Link } from "react-router-dom";
|
||||
import {
|
||||
useClient,
|
||||
@ -22,6 +22,7 @@ import {
|
||||
usePublicRooms,
|
||||
createRoom,
|
||||
roomAliasFromRoomName,
|
||||
useInteractiveRegistration,
|
||||
} from "./ConferenceCallManagerHooks";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||
import styles from "./Home.module.css";
|
||||
@ -43,9 +44,10 @@ export function Home() {
|
||||
loading,
|
||||
error,
|
||||
client,
|
||||
register,
|
||||
} = useClient();
|
||||
|
||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
||||
|
||||
const history = useHistory();
|
||||
const [creatingRoom, setCreatingRoom] = useState(false);
|
||||
const [createRoomError, setCreateRoomError] = useState();
|
||||
@ -118,6 +120,7 @@ export function Home() {
|
||||
createRoomError={createRoomError}
|
||||
creatingRoom={creatingRoom}
|
||||
onJoinRoom={onJoinRoom}
|
||||
privacyPolicyUrl={privacyPolicyUrl}
|
||||
/>
|
||||
) : (
|
||||
<RegisteredView
|
||||
@ -143,7 +146,25 @@ function UnregisteredView({
|
||||
createRoomError,
|
||||
creatingRoom,
|
||||
onJoinRoom,
|
||||
privacyPolicyUrl,
|
||||
}) {
|
||||
const acceptTermsRef = useRef();
|
||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!acceptTermsRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!acceptTerms) {
|
||||
acceptTermsRef.current.setCustomValidity(
|
||||
"You must accept the terms to continue."
|
||||
);
|
||||
} else {
|
||||
acceptTermsRef.current.setCustomValidity("");
|
||||
}
|
||||
}, [acceptTerms]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.home, styles.fullWidth)}>
|
||||
<Header className={styles.header}>
|
||||
@ -202,6 +223,20 @@ function UnregisteredView({
|
||||
placeholder="Room Name"
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="acceptTerms"
|
||||
type="checkbox"
|
||||
name="acceptTerms"
|
||||
onChange={(e) => setAcceptTerms(e.target.checked)}
|
||||
checked={acceptTerms}
|
||||
label="Accept Privacy Policy"
|
||||
ref={acceptTermsRef}
|
||||
/>
|
||||
<a target="_blank" href={privacyPolicyUrl}>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</FieldRow>
|
||||
{createRoomError && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
||||
|
@ -1,6 +1,7 @@
|
||||
.fieldRow {
|
||||
display: flex;
|
||||
margin-bottom: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field {
|
||||
|
@ -20,14 +20,14 @@ import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
|
||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||
import { Button } from "./button";
|
||||
import {
|
||||
useClient,
|
||||
defaultHomeserver,
|
||||
defaultHomeserverHost,
|
||||
useInteractiveLogin,
|
||||
} from "./ConferenceCallManagerHooks";
|
||||
import styles from "./LoginPage.module.css";
|
||||
|
||||
export function LoginPage() {
|
||||
const { login } = useClient();
|
||||
const [_, login] = useInteractiveLogin();
|
||||
const [homeserver, setHomeServer] = useState(defaultHomeserver);
|
||||
const usernameRef = useRef();
|
||||
const passwordRef = useRef();
|
||||
|
@ -18,7 +18,11 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||
import { Button } from "./button";
|
||||
import { useClient, defaultHomeserverHost } from "./ConferenceCallManagerHooks";
|
||||
import {
|
||||
useClient,
|
||||
defaultHomeserverHost,
|
||||
useInteractiveRegistration,
|
||||
} from "./ConferenceCallManagerHooks";
|
||||
import styles from "./LoginPage.module.css";
|
||||
import { ReactComponent as Logo } from "./icons/LogoLarge.svg";
|
||||
import { LoadingView } from "./FullScreenView";
|
||||
@ -27,18 +31,20 @@ export function RegisterPage() {
|
||||
const {
|
||||
loading,
|
||||
client,
|
||||
register,
|
||||
changePassword,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
} = useClient();
|
||||
const confirmPasswordRef = useRef();
|
||||
const acceptTermsRef = useRef();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [registering, setRegistering] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirmation, setPasswordConfirmation] = useState("");
|
||||
const [acceptTerms, setAcceptTerms] = useState(false);
|
||||
const [{ privacyPolicyUrl }, register] = useInteractiveRegistration();
|
||||
|
||||
const onSubmitRegisterForm = useCallback(
|
||||
(e) => {
|
||||
@ -47,8 +53,9 @@ export function RegisterPage() {
|
||||
const userName = data.get("userName");
|
||||
const password = data.get("password");
|
||||
const passwordConfirmation = data.get("passwordConfirmation");
|
||||
const acceptTerms = data.get("acceptTerms");
|
||||
|
||||
if (password !== passwordConfirmation) {
|
||||
if (password !== passwordConfirmation || !acceptTerms) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -97,6 +104,20 @@ export function RegisterPage() {
|
||||
}
|
||||
}, [password, passwordConfirmation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!acceptTermsRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!acceptTerms) {
|
||||
acceptTermsRef.current.setCustomValidity(
|
||||
"You must accept the terms to continue."
|
||||
);
|
||||
} else {
|
||||
acceptTermsRef.current.setCustomValidity("");
|
||||
}
|
||||
}, [acceptTerms]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser) {
|
||||
history.push("/");
|
||||
@ -156,6 +177,20 @@ export function RegisterPage() {
|
||||
ref={confirmPasswordRef}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="acceptTerms"
|
||||
type="checkbox"
|
||||
name="acceptTerms"
|
||||
onChange={(e) => setAcceptTerms(e.target.checked)}
|
||||
checked={acceptTerms}
|
||||
label="Accept Privacy Policy"
|
||||
ref={acceptTermsRef}
|
||||
/>
|
||||
<a target="_blank" href={privacyPolicyUrl}>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
Loading…
Reference in New Issue
Block a user