Clean up old auth logic

This commit is contained in:
Robert Long
2022-01-05 16:47:53 -08:00
parent 8a452d80e2
commit 0adc4b3d66
7 changed files with 151 additions and 324 deletions

View File

@@ -21,9 +21,8 @@ import React, {
createContext, createContext,
useMemo, useMemo,
useContext, useContext,
useRef,
} from "react"; } from "react";
import matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index"; import matrix from "matrix-js-sdk/src/browser-index";
import { import {
GroupCallIntent, GroupCallIntent,
GroupCallType, GroupCallType,
@@ -53,13 +52,9 @@ function waitForSync(client) {
}); });
} }
async function initClient(clientOptions, guest) { export async function initClient(clientOptions) {
const client = matrix.createClient(clientOptions); const client = matrix.createClient(clientOptions);
if (guest) {
client.setGuest(true);
}
await client.startClient({ await client.startClient({
// dirty hack to reduce chance of gappy syncs // dirty hack to reduce chance of gappy syncs
// should be fixed by spotting gaps and backpaginating // should be fixed by spotting gaps and backpaginating
@@ -110,13 +105,12 @@ export async function fetchGroupCall(
export function ClientProvider({ children }) { export function ClientProvider({ children }) {
const history = useHistory(); const history = useHistory();
const [ const [
{ loading, isAuthenticated, isPasswordlessUser, isGuest, client, userName }, { loading, isAuthenticated, isPasswordlessUser, client, userName },
setState, setState,
] = useState({ ] = useState({
loading: true, loading: true,
isAuthenticated: false, isAuthenticated: false,
isPasswordlessUser: false, isPasswordlessUser: false,
isGuest: false,
client: undefined, client: undefined,
userName: null, userName: null,
}); });
@@ -131,20 +125,16 @@ export function ClientProvider({ children }) {
user_id, user_id,
device_id, device_id,
access_token, access_token,
guest,
passwordlessUser, passwordlessUser,
tempPassword, tempPassword,
} = JSON.parse(authStore); } = JSON.parse(authStore);
const client = await initClient( const client = await initClient({
{
baseUrl: defaultHomeserver, baseUrl: defaultHomeserver,
accessToken: access_token, accessToken: access_token,
userId: user_id, userId: user_id,
deviceId: device_id, deviceId: device_id,
}, });
guest
);
localStorage.setItem( localStorage.setItem(
"matrix-auth-store", "matrix-auth-store",
@@ -152,16 +142,16 @@ export function ClientProvider({ children }) {
user_id, user_id,
device_id, device_id,
access_token, access_token,
guest,
passwordlessUser, passwordlessUser,
tempPassword, tempPassword,
}) })
); );
return { client, guest, passwordlessUser }; return { client, passwordlessUser };
} }
return { client: undefined, guest: false }; return { client: undefined };
} catch (err) { } catch (err) {
localStorage.removeItem("matrix-auth-store"); localStorage.removeItem("matrix-auth-store");
throw err; throw err;
@@ -169,13 +159,12 @@ export function ClientProvider({ children }) {
} }
restore() restore()
.then(({ client, guest, passwordlessUser }) => { .then(({ client, passwordlessUser }) => {
setState({ setState({
client, client,
loading: false, loading: false,
isAuthenticated: !!client, isAuthenticated: !!client,
isPasswordlessUser: !!passwordlessUser, isPasswordlessUser: !!passwordlessUser,
isGuest: guest,
userName: client?.getUserIdLocalpart(), userName: client?.getUserIdLocalpart(),
}); });
}) })
@@ -185,168 +174,11 @@ export function ClientProvider({ children }) {
loading: false, loading: false,
isAuthenticated: false, isAuthenticated: false,
isPasswordlessUser: false, isPasswordlessUser: false,
isGuest: false,
userName: null, userName: null,
}); });
}); });
}, []); }, []);
const login = useCallback(async (homeserver, username, password) => {
try {
let loginHomeserverUrl = homeserver.trim();
if (!loginHomeserverUrl.includes("://")) {
loginHomeserverUrl = "https://" + loginHomeserverUrl;
}
try {
const wellKnownUrl = new URL(
"/.well-known/matrix/client",
window.location
);
const response = await fetch(wellKnownUrl);
const config = await response.json();
if (config["m.homeserver"]) {
loginHomeserverUrl = config["m.homeserver"];
}
} catch (error) {}
const registrationClient = matrix.createClient(loginHomeserverUrl);
const { user_id, device_id, access_token } =
await registrationClient.loginWithPassword(username, password);
const client = await initClient({
baseUrl: loginHomeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token })
);
setState({
client,
loading: false,
isAuthenticated: true,
isPasswordlessUser: false,
isGuest: false,
userName: client.getUserIdLocalpart(),
});
} catch (err) {
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
isAuthenticated: false,
isPasswordlessUser: false,
isGuest: false,
userName: null,
});
throw err;
}
}, []);
const registerGuest = useCallback(async () => {
try {
const registrationClient = matrix.createClient(defaultHomeserver);
const { user_id, device_id, access_token } =
await registrationClient.registerGuest({});
const client = await initClient(
{
baseUrl: defaultHomeserver,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
},
true
);
await client.setProfileInfo("displayname", {
displayname: `Guest ${client.getUserIdLocalpart()}`,
});
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token, guest: true })
);
setState({
client,
loading: false,
isAuthenticated: true,
isGuest: true,
isPasswordlessUser: false,
userName: client.getUserIdLocalpart(),
});
} catch (err) {
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
isAuthenticated: false,
isPasswordlessUser: false,
isGuest: false,
userName: null,
});
throw err;
}
}, []);
const register = useCallback(async (username, password, passwordlessUser) => {
try {
const registrationClient = matrix.createClient(defaultHomeserver);
const { user_id, device_id, access_token } =
await registrationClient.register(username, password, null, {
type: "m.login.dummy",
});
const client = await initClient({
baseUrl: defaultHomeserver,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
const session = { user_id, device_id, access_token, passwordlessUser };
if (passwordlessUser) {
session.tempPassword = password;
}
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
setState({
client,
loading: false,
isGuest: false,
isAuthenticated: true,
isPasswordlessUser: passwordlessUser,
userName: client.getUserIdLocalpart(),
});
return client;
} catch (err) {
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
isGuest: false,
isAuthenticated: false,
isPasswordlessUser: false,
userName: null,
});
throw err;
}
}, []);
const changePassword = useCallback( const changePassword = useCallback(
async (password) => { async (password) => {
const { tempPassword, passwordlessUser, ...existingSession } = JSON.parse( const { tempPassword, passwordlessUser, ...existingSession } = JSON.parse(
@@ -377,7 +209,6 @@ export function ClientProvider({ children }) {
setState({ setState({
client, client,
loading: false, loading: false,
isGuest: false,
isAuthenticated: true, isAuthenticated: true,
isPasswordlessUser: false, isPasswordlessUser: false,
userName: client.getUserIdLocalpart(), userName: client.getUserIdLocalpart(),
@@ -395,7 +226,6 @@ export function ClientProvider({ children }) {
loading: false, loading: false,
isAuthenticated: true, isAuthenticated: true,
isPasswordlessUser: !!session.passwordlessUser, isPasswordlessUser: !!session.passwordlessUser,
isGuest: false,
userName: client.getUserIdLocalpart(), userName: client.getUserIdLocalpart(),
}); });
} else { } else {
@@ -406,7 +236,6 @@ export function ClientProvider({ children }) {
loading: false, loading: false,
isAuthenticated: false, isAuthenticated: false,
isPasswordlessUser: false, isPasswordlessUser: false,
isGuest: false,
userName: null, userName: null,
}); });
} }
@@ -422,11 +251,7 @@ export function ClientProvider({ children }) {
loading, loading,
isAuthenticated, isAuthenticated,
isPasswordlessUser, isPasswordlessUser,
isGuest,
client, client,
login,
registerGuest,
register,
changePassword, changePassword,
logout, logout,
userName, userName,
@@ -436,11 +261,7 @@ export function ClientProvider({ children }) {
loading, loading,
isAuthenticated, isAuthenticated,
isPasswordlessUser, isPasswordlessUser,
isGuest,
client, client,
login,
registerGuest,
register,
changePassword, changePassword,
logout, logout,
userName, userName,
@@ -496,11 +317,6 @@ export async function createRoom(client, name) {
}, },
}); });
await client.setGuestAccess(room_id, {
allowJoin: true,
allowRead: true,
});
await client.createGroupCall( await client.createGroupCall(
room_id, room_id,
GroupCallType.Video, GroupCallType.Video,
@@ -747,126 +563,3 @@ export function useProfile(client) {
return { loading, error, displayName, avatarUrl, saveProfile, success }; 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;
const recaptchaKey = error.data?.params["m.login.recaptcha"]?.public_key;
if (privacyPolicyUrl || recaptchaKey) {
setState((prev) => ({ ...prev, privacyPolicyUrl, recaptchaKey }));
}
});
}, []);
const register = useCallback(
async (username, password, recaptchaResponse, passwordlessUser) => {
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 (status.error) {
throw new Error(error);
}
if (nextStage === "m.login.terms") {
interactiveAuth.submitAuthDict({
type: "m.login.terms",
});
} else if (nextStage === "m.login.recaptcha") {
interactiveAuth.submitAuthDict({
type: "m.login.recaptcha",
response: recaptchaResponse,
});
}
},
});
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,
});
const session = { user_id, device_id, access_token, passwordlessUser };
if (passwordlessUser) {
session.tempPassword = password;
}
setClient(client, session);
return client;
},
[]
);
return [state, register];
}

View File

@@ -22,9 +22,9 @@ import { Button } from "../button";
import { import {
defaultHomeserver, defaultHomeserver,
defaultHomeserverHost, defaultHomeserverHost,
useInteractiveLogin,
} from "../ConferenceCallManagerHooks"; } from "../ConferenceCallManagerHooks";
import styles from "./LoginPage.module.css"; import styles from "./LoginPage.module.css";
import { useInteractiveLogin } from "./useInteractiveLogin";
export function LoginPage() { export function LoginPage() {
const [_, login] = useInteractiveLogin(); const [_, login] = useInteractiveLogin();

View File

@@ -21,8 +21,8 @@ import { Button } from "../button";
import { import {
useClient, useClient,
defaultHomeserverHost, defaultHomeserverHost,
useInteractiveRegistration,
} from "../ConferenceCallManagerHooks"; } from "../ConferenceCallManagerHooks";
import { useInteractiveRegistration } from "./useInteractiveRegistration";
import styles from "./LoginPage.module.css"; import styles from "./LoginPage.module.css";
import { ReactComponent as Logo } from "../icons/LogoLarge.svg"; import { ReactComponent as Logo } from "../icons/LogoLarge.svg";
import { LoadingView } from "../FullScreenView"; import { LoadingView } from "../FullScreenView";

View File

@@ -0,0 +1,48 @@
import matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index";
import { useState, useCallback } from "react";
import {
useClient,
initClient,
defaultHomeserver,
} from "../ConferenceCallManagerHooks";
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,
});
},
});
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];
}

View File

@@ -0,0 +1,86 @@
import matrix, { InteractiveAuth } from "matrix-js-sdk/src/browser-index";
import { useState, useEffect, useCallback, useRef } from "react";
import {
useClient,
initClient,
defaultHomeserver,
} from "../ConferenceCallManagerHooks";
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;
const recaptchaKey = error.data?.params["m.login.recaptcha"]?.public_key;
if (privacyPolicyUrl || recaptchaKey) {
setState((prev) => ({ ...prev, privacyPolicyUrl, recaptchaKey }));
}
});
}, []);
const register = useCallback(
async (username, password, recaptchaResponse, passwordlessUser) => {
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 (status.error) {
throw new Error(error);
}
if (nextStage === "m.login.terms") {
interactiveAuth.submitAuthDict({
type: "m.login.terms",
});
} else if (nextStage === "m.login.recaptcha") {
interactiveAuth.submitAuthDict({
type: "m.login.recaptcha",
response: recaptchaResponse,
});
}
},
});
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,
});
const session = { user_id, device_id, access_token, passwordlessUser };
if (passwordlessUser) {
session.tempPassword = password;
}
setClient(client, session);
return client;
},
[]
);
return [state, register];
}

View File

@@ -7,9 +7,9 @@ import { Button } from "../button";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { import {
createRoom, createRoom,
useInteractiveRegistration,
roomAliasFromRoomName, roomAliasFromRoomName,
} from "../ConferenceCallManagerHooks"; } from "../ConferenceCallManagerHooks";
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
import { useModalTriggerState } from "../Modal"; import { useModalTriggerState } from "../Modal";
import { JoinExistingCallModal } from "../JoinExistingCallModal"; import { JoinExistingCallModal } from "../JoinExistingCallModal";
import { useRecaptcha } from "../auth/useRecaptcha"; import { useRecaptcha } from "../auth/useRecaptcha";

View File

@@ -7,7 +7,7 @@ import { useLocation } from "react-router-dom";
import { useRecaptcha } from "../auth/useRecaptcha"; import { useRecaptcha } from "../auth/useRecaptcha";
import { FieldRow, InputField, ErrorMessage } from "../Input"; import { FieldRow, InputField, ErrorMessage } from "../Input";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { useInteractiveRegistration } from "../ConferenceCallManagerHooks"; import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
import { Form } from "../form/Form"; import { Form } from "../form/Form";
export function RoomAuthView() { export function RoomAuthView() {