import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin"; import { MatrixError, displayError } from "../utils/error"; import { fetchAuthenticatedMedia } from "../utils/fetchMedia"; import { FetchConfig, ClearConfig, GetConfig } from "../utils/config"; import decodeURLComponent from "../utils/decodeURLComponent"; const authProvider: AuthProvider = { // called when the user attempts to log in login: async ({ base_url, username, password, loginToken, accessToken, }: { base_url: string; username: string; password: string; loginToken: string; accessToken: string; }) => { console.log("login "); let options: Options = { method: "POST", body: JSON.stringify( Object.assign( { device_id: localStorage.getItem("device_id"), initial_device_display_name: "Synapse Admin", }, loginToken ? { type: "m.login.token", token: loginToken, } : { type: "m.login.password", identifier: { type: "m.id.user", user: username, }, password: password, } ) ), }; // use the base_url from login instead of the well_known entry from the // server, since the admin might want to access the admin API via some // private address if (!base_url) { // there is some kind of bug with base_url being present in the form, but not submitted // ref: https://github.com/etkecc/synapse-admin/issues/14 localStorage.removeItem("base_url") throw new Error("Homeserver URL is required."); } base_url = base_url.replace(/\/+$/g, ""); localStorage.setItem("base_url", base_url); const decoded_base_url = decodeURLComponent(base_url); let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login"); let response; try { if (accessToken) { // this a login with an already obtained access token, let's just validate it options = { headers: new Headers({ Accept: 'application/json', Authorization: `Bearer ${accessToken}`, }), }; } response = await fetchUtils.fetchJson(login_api_url, options); const json = response.json; localStorage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server); localStorage.setItem("user_id", json.user_id); localStorage.setItem("access_token", accessToken ? accessToken : json.access_token); localStorage.setItem("device_id", json.device_id); localStorage.setItem("login_type", accessToken ? "accessToken" : "credentials"); await FetchConfig(); const config = GetConfig(); let pageToRedirectTo = "/"; if (config && config.etkeccAdmin) { pageToRedirectTo = "/server_status"; } return Promise.resolve({redirectTo: pageToRedirectTo}); } catch(err) { const error = err as HttpError; const errorStatus = error.status; const errorBody = error.body as MatrixError; const errMsg = !!errorBody?.errcode ? displayError(errorBody.errcode, errorStatus, errorBody.error) : displayError("M_INVALID", errorStatus, error.message); return Promise.reject( new HttpError( errMsg, errorStatus, ) ); } }, getIdentity: async () => { const access_token = localStorage.getItem("access_token"); const user_id = localStorage.getItem("user_id"); const base_url = localStorage.getItem("base_url"); if (typeof access_token !== "string" || typeof user_id !== "string" || typeof base_url !== "string") { return Promise.reject(); } const options: Options = { headers: new Headers({ Accept: "application/json", Authorization: `Bearer ${access_token}`, }), }; const whoami_api_url = base_url + `/_matrix/client/v3/profile/${user_id}`; try { let avatar_url = ""; const response = await fetchUtils.fetchJson(whoami_api_url, options); if (response.json.avatar_url) { const mediaresp = await fetchAuthenticatedMedia(response.json.avatar_url, "thumbnail"); const blob = await mediaresp.blob(); avatar_url = URL.createObjectURL(blob); } return Promise.resolve({ id: user_id, fullName: response.json.displayname, avatar: avatar_url, }); } catch (err) { console.log("Error getting identity", err); return Promise.reject(); } }, // called when the user clicks on the logout button logout: async () => { console.log("logout"); const logout_api_url = localStorage.getItem("base_url") + "/_matrix/client/v3/logout"; const access_token = localStorage.getItem("access_token"); const options: Options = { method: "POST", user: { authenticated: true, token: `Bearer ${access_token}`, }, }; if (typeof access_token === "string") { try { await fetchUtils.fetchJson(logout_api_url, options); } catch (err) { console.log("Error logging out", err); } finally { ClearConfig(); } } }, // called when the API returns an error checkError: (err: HttpError) => { const errorBody = err.body as MatrixError; const status = err.status; if (status === 401 || status === 403) { return Promise.reject({message: displayError(errorBody.errcode, status, errorBody.error)}); } return Promise.resolve(); }, // called when the user navigates to a new location, to check for authentication checkAuth: () => { const access_token = localStorage.getItem("access_token"); return typeof access_token === "string" ? Promise.resolve() : Promise.reject(); }, // called when the user navigates to a new location, to check for permissions / roles getPermissions: () => Promise.resolve(), }; export default authProvider;