mirror of
https://github.com/etkecc/synapse-admin.git
synced 2025-04-08 05:23:33 +03:00
Add option for access token login (#58)
* Fix SSO login flow, redirect is done after auth * Add accessToken login * Add confirmation for session destroy on accessToken logout * add translations, fix tests, minor renaming * update readme
This commit is contained in:
parent
19302466ef
commit
0d021021df
15 changed files with 709 additions and 542 deletions
|
@ -65,6 +65,7 @@ The following changes are already implemented:
|
||||||
* [Provide options to delete media and redact events on user erase](https://github.com/etkecc/synapse-admin/pull/49)
|
* [Provide options to delete media and redact events on user erase](https://github.com/etkecc/synapse-admin/pull/49)
|
||||||
* [Authenticated Media support](https://github.com/etkecc/synapse-admin/pull/51)
|
* [Authenticated Media support](https://github.com/etkecc/synapse-admin/pull/51)
|
||||||
* [Better media preview/download](https://github.com/etkecc/synapse-admin/pull/53)
|
* [Better media preview/download](https://github.com/etkecc/synapse-admin/pull/53)
|
||||||
|
* [Login with access token](https://github.com/etkecc/synapse-admin/pull/58)
|
||||||
|
|
||||||
_the list will be updated as new changes are added_
|
_the list will be updated as new changes are added_
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,60 @@
|
||||||
import { Layout, Menu } from 'react-admin';
|
import { AppBar, Confirm, Layout, Logout, Menu, useLogout, UserMenu } from "react-admin";
|
||||||
import LiveHelpIcon from '@mui/icons-material/LiveHelp';
|
import LiveHelpIcon from "@mui/icons-material/LiveHelp";
|
||||||
|
import { LoginMethod } from "../pages/LoginPage";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const DEFAULT_SUPPORT_LINK = "https://github.com/etkecc/synapse-admin/issues";
|
const DEFAULT_SUPPORT_LINK = "https://github.com/etkecc/synapse-admin/issues";
|
||||||
const supportLink = (): string => {
|
const supportLink = (): string => {
|
||||||
try {
|
try {
|
||||||
new URL(localStorage.getItem("support_url") || ''); // Check if the URL is valid
|
new URL(localStorage.getItem("support_url") || ""); // Check if the URL is valid
|
||||||
return localStorage.getItem("support_url") || DEFAULT_SUPPORT_LINK;
|
return localStorage.getItem("support_url") || DEFAULT_SUPPORT_LINK;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return DEFAULT_SUPPORT_LINK;
|
return DEFAULT_SUPPORT_LINK;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AdminUserMenu = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const logout = useLogout();
|
||||||
|
const checkLoginType = (ev: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const loginType: LoginMethod = (localStorage.getItem("login_type") || "credentials") as LoginMethod;
|
||||||
|
if (loginType === "accessToken") {
|
||||||
|
ev.stopPropagation();
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
setOpen(false);
|
||||||
|
logout();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
localStorage.removeItem("access_token");
|
||||||
|
localStorage.removeItem("login_type");
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserMenu>
|
||||||
|
<div onClickCapture={checkLoginType}>
|
||||||
|
<Logout />
|
||||||
|
</div>
|
||||||
|
<Confirm
|
||||||
|
isOpen={open}
|
||||||
|
title="synapseadmin.auth.logout_acces_token_dialog.title"
|
||||||
|
content="synapseadmin.auth.logout_acces_token_dialog.content"
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onClose={handleDialogClose}
|
||||||
|
confirm="synapseadmin.auth.logout_acces_token_dialog.confirm"
|
||||||
|
cancel="synapseadmin.auth.logout_acces_token_dialog.cancel"
|
||||||
|
/>
|
||||||
|
</UserMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AdminAppBar = () => <AppBar userMenu={<AdminUserMenu />} />;
|
||||||
|
|
||||||
const AdminMenu = () => (
|
const AdminMenu = () => (
|
||||||
<Menu>
|
<Menu>
|
||||||
|
@ -20,7 +64,7 @@ const AdminMenu = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AdminLayout = ({ children }) => (
|
export const AdminLayout = ({ children }) => (
|
||||||
<Layout menu={AdminMenu}>
|
<Layout appBar={AdminAppBar} menu={AdminMenu}>
|
||||||
{children}
|
{children}
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
58
src/components/LoginFormBox.tsx
Normal file
58
src/components/LoginFormBox.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
const LoginFormBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
minHeight: "calc(100vh - 1rem)",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background: "url(./images/floating-cogs.svg)",
|
||||||
|
backgroundColor: "#f9f9f9",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
|
||||||
|
[`& .card`]: {
|
||||||
|
width: "30rem",
|
||||||
|
marginTop: "6rem",
|
||||||
|
marginBottom: "6rem",
|
||||||
|
},
|
||||||
|
[`& .avatar`]: {
|
||||||
|
margin: "1rem",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
[`& .icon`]: {
|
||||||
|
backgroundColor: theme.palette.grey[500],
|
||||||
|
},
|
||||||
|
[`& .hint`]: {
|
||||||
|
marginTop: "1em",
|
||||||
|
marginBottom: "1em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: theme.palette.grey[600],
|
||||||
|
},
|
||||||
|
[`& .form`]: {
|
||||||
|
padding: "0 1rem 1rem 1rem",
|
||||||
|
},
|
||||||
|
[`& .select`]: {
|
||||||
|
marginBottom: "2rem",
|
||||||
|
},
|
||||||
|
[`& .actions`]: {
|
||||||
|
padding: "0 1rem 1rem 1rem",
|
||||||
|
},
|
||||||
|
[`& .serverVersion`]: {
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
|
marginLeft: "0.5rem",
|
||||||
|
},
|
||||||
|
[`& .matrixVersions`]: {
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
marginLeft: "0.5rem",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default LoginFormBox;
|
|
@ -22,6 +22,14 @@ const de: SynapseTranslationMessages = {
|
||||||
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
|
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
|
||||||
url_error: "Keine gültige Matrix Server URL",
|
url_error: "Keine gültige Matrix Server URL",
|
||||||
sso_sign_in: "Anmeldung mit SSO",
|
sso_sign_in: "Anmeldung mit SSO",
|
||||||
|
credentials: "Anmeldedaten",
|
||||||
|
access_token: "Zugriffstoken",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "Sie verwenden ein bestehendes Matrix-Zugriffstoken.",
|
||||||
|
content: "Möchten Sie diese Sitzung (die anderswo, z.B. in einem Matrix-Client, verwendet werden könnte) beenden oder sich nur vom Admin-Panel abmelden?",
|
||||||
|
confirm: "Sitzung beenden",
|
||||||
|
cancel: "Nur vom Admin-Panel abmelden",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
|
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
|
||||||
|
|
|
@ -14,6 +14,14 @@ const en: SynapseTranslationMessages = {
|
||||||
protocol_error: "URL has to start with 'http://' or 'https://'",
|
protocol_error: "URL has to start with 'http://' or 'https://'",
|
||||||
url_error: "Not a valid Matrix server URL",
|
url_error: "Not a valid Matrix server URL",
|
||||||
sso_sign_in: "Sign in with SSO",
|
sso_sign_in: "Sign in with SSO",
|
||||||
|
credentials: "Credentials",
|
||||||
|
access_token: "Access token",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "You are using an existing Matrix access token.",
|
||||||
|
content: "Do you want to destroy this session (that could be used elsewhere, e.g. in a Matrix client) or just logout from the admin panel?",
|
||||||
|
confirm: "Destroy session",
|
||||||
|
cancel: "Just logout from admin panel",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
|
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
|
||||||
|
|
|
@ -13,6 +13,14 @@ const fa: SynapseTranslationMessages = {
|
||||||
protocol_error: "URL باید با 'http://' یا 'https://' شروع شود",
|
protocol_error: "URL باید با 'http://' یا 'https://' شروع شود",
|
||||||
url_error: "آدرس وارد شده یک سرور معتبر نیست",
|
url_error: "آدرس وارد شده یک سرور معتبر نیست",
|
||||||
sso_sign_in: "با SSO وارد شوید",
|
sso_sign_in: "با SSO وارد شوید",
|
||||||
|
credentials: "اعتبارنامه",
|
||||||
|
access_token: "توکن دسترسی",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "شما در حال استفاده از یک نشانه دسترسی ماتریکس موجود هستید.",
|
||||||
|
content: "آیا میخواهید این جلسه (که میتواند در جای دیگر، مانند یک کلاینت ماتریکس استفاده شود) را نابود کنید یا فقط از پنل مدیریت خارج شوید؟",
|
||||||
|
confirm: "نابودی جلسه",
|
||||||
|
cancel: "فقط خروج از پنل مدیریت",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "بخش محلی یک شناسه کاربری ماتریکس بدون سرور خانگی.",
|
invalid_user_id: "بخش محلی یک شناسه کاربری ماتریکس بدون سرور خانگی.",
|
||||||
|
|
|
@ -13,6 +13,14 @@ const fr: SynapseTranslationMessages = {
|
||||||
protocol_error: "L'URL doit commencer par « http:// » ou « https:// »",
|
protocol_error: "L'URL doit commencer par « http:// » ou « https:// »",
|
||||||
url_error: "L'URL du serveur Matrix n'est pas valide",
|
url_error: "L'URL du serveur Matrix n'est pas valide",
|
||||||
sso_sign_in: "Se connecter avec l’authentification unique",
|
sso_sign_in: "Se connecter avec l’authentification unique",
|
||||||
|
credentials: "Identifiants",
|
||||||
|
access_token: "Jeton d'accès",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "Vous utilisez un jeton d'accès Matrix existant.",
|
||||||
|
content: "Voulez-vous détruire cette session (qui pourrait être utilisée ailleurs, par exemple dans un client Matrix) ou simplement vous déconnecter du panneau d'administration?",
|
||||||
|
confirm: "Détruire la session",
|
||||||
|
cancel: "Se déconnecter simplement du panneau d'administration",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur d’accueil.",
|
invalid_user_id: "Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur d’accueil.",
|
||||||
|
|
8
src/i18n/index.d.ts
vendored
8
src/i18n/index.d.ts
vendored
|
@ -11,6 +11,14 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
||||||
protocol_error: string;
|
protocol_error: string;
|
||||||
url_error: string;
|
url_error: string;
|
||||||
sso_sign_in: string;
|
sso_sign_in: string;
|
||||||
|
credentials: string;
|
||||||
|
access_token: string;
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
confirm: string;
|
||||||
|
cancel: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: string;
|
invalid_user_id: string;
|
||||||
|
|
|
@ -13,6 +13,14 @@ const it: SynapseTranslationMessages = {
|
||||||
protocol_error: "L'URL deve iniziare per 'http://' o 'https://'",
|
protocol_error: "L'URL deve iniziare per 'http://' o 'https://'",
|
||||||
url_error: "URL del server Matrix non valido",
|
url_error: "URL del server Matrix non valido",
|
||||||
sso_sign_in: "Accedi con SSO",
|
sso_sign_in: "Accedi con SSO",
|
||||||
|
credentials: "Credenziali",
|
||||||
|
access_token: "Token di accesso",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "Stai utilizzando un token di accesso Matrix esistente.",
|
||||||
|
content: "Vuoi distruggere questa sessione (che potrebbe essere utilizzata altrove, ad esempio in un client Matrix) o semplicemente disconnetterti dal pannello di amministrazione?",
|
||||||
|
confirm: "Distruggi sessione",
|
||||||
|
cancel: "Disconnetti solo dal pannello di amministrazione",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "ID utente non valido su questo homeserver.",
|
invalid_user_id: "ID utente non valido su questo homeserver.",
|
||||||
|
|
|
@ -22,6 +22,14 @@ const ru: SynapseTranslationMessages = {
|
||||||
protocol_error: "Адрес должен начинаться с 'http://' или 'https://'",
|
protocol_error: "Адрес должен начинаться с 'http://' или 'https://'",
|
||||||
url_error: "Неверный адрес сервера Matrix",
|
url_error: "Неверный адрес сервера Matrix",
|
||||||
sso_sign_in: "Вход через SSO",
|
sso_sign_in: "Вход через SSO",
|
||||||
|
credentials: "Учетные данные",
|
||||||
|
access_token: "Токен доступа",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "Вы используете существующий токен доступа Matrix.",
|
||||||
|
content: "Вы хотите завершить эту сессию (которая может быть использована в другом месте, например, в клиенте Matrix) или просто выйти из панели администрирования?",
|
||||||
|
confirm: "Завершить сессию",
|
||||||
|
cancel: "Просто выйти из панели администрирования",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Локальная часть ID пользователя Matrix без адреса домашнего сервера.",
|
invalid_user_id: "Локальная часть ID пользователя Matrix без адреса домашнего сервера.",
|
||||||
|
@ -421,6 +429,6 @@ const ru: SynapseTranslationMessages = {
|
||||||
},
|
},
|
||||||
helper: { length: "Длина токена, если токен не задан." },
|
helper: { length: "Длина токена, если токен не задан." },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export default ru;
|
export default ru;
|
||||||
|
|
|
@ -21,6 +21,14 @@ const zh: SynapseTranslationMessages = {
|
||||||
protocol_error: "URL 需要以'http://'或'https://'作为起始",
|
protocol_error: "URL 需要以'http://'或'https://'作为起始",
|
||||||
url_error: "不是一个有效的 Matrix 服务器地址",
|
url_error: "不是一个有效的 Matrix 服务器地址",
|
||||||
sso_sign_in: "使用 SSO 登录",
|
sso_sign_in: "使用 SSO 登录",
|
||||||
|
credentials: "凭证",
|
||||||
|
access_token: "访问令牌",
|
||||||
|
logout_acces_token_dialog: {
|
||||||
|
title: "您正在使用现有的 Matrix 访问令牌。",
|
||||||
|
content: "您想销毁此会话(可能在其他地方使用,例如在 Matrix 客户端中)还是仅从管理面板退出?",
|
||||||
|
confirm: "销毁会话",
|
||||||
|
cancel: "仅从管理面板退出",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
|
invalid_user_id: "必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
|
||||||
|
|
|
@ -9,8 +9,12 @@ import storage from "./storage";
|
||||||
fetch("config.json")
|
fetch("config.json")
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(props => {
|
.then(props => {
|
||||||
|
if (props.asManagedUsers) {
|
||||||
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
|
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
|
||||||
|
}
|
||||||
|
if (props.supportURL) {
|
||||||
storage.setItem("support_url", props.supportURL);
|
storage.setItem("support_url", props.supportURL);
|
||||||
|
}
|
||||||
return createRoot(document.getElementById("root")).render(
|
return createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<AppContext.Provider value={props}>
|
<AppContext.Provider value={props}>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Typography } from "@mui/material";
|
import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Tab, Tabs, Typography } from "@mui/material";
|
||||||
import { styled } from "@mui/material/styles";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormDataConsumer,
|
FormDataConsumer,
|
||||||
|
@ -17,7 +16,7 @@ import {
|
||||||
useLocales,
|
useLocales,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import LoginFormBox from "../components/LoginFormBox";
|
||||||
import { useAppContext } from "../AppContext";
|
import { useAppContext } from "../AppContext";
|
||||||
import {
|
import {
|
||||||
getServerVersion,
|
getServerVersion,
|
||||||
|
@ -29,66 +28,18 @@ import {
|
||||||
} from "../synapse/synapse";
|
} from "../synapse/synapse";
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
|
|
||||||
const FormBox = styled(Box)(({ theme }) => ({
|
export type LoginMethod = "credentials" | "accessToken";
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
minHeight: "calc(100vh - 1rem)",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
background: "url(./images/floating-cogs.svg)",
|
|
||||||
backgroundColor: "#f9f9f9",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
backgroundSize: "cover",
|
|
||||||
|
|
||||||
[`& .card`]: {
|
|
||||||
width: "30rem",
|
|
||||||
marginTop: "6rem",
|
|
||||||
marginBottom: "6rem",
|
|
||||||
},
|
|
||||||
[`& .avatar`]: {
|
|
||||||
margin: "1rem",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
[`& .icon`]: {
|
|
||||||
backgroundColor: theme.palette.grey[500],
|
|
||||||
},
|
|
||||||
[`& .hint`]: {
|
|
||||||
marginTop: "1em",
|
|
||||||
marginBottom: "1em",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
color: theme.palette.grey[600],
|
|
||||||
},
|
|
||||||
[`& .form`]: {
|
|
||||||
padding: "0 1rem 1rem 1rem",
|
|
||||||
},
|
|
||||||
[`& .select`]: {
|
|
||||||
marginBottom: "2rem",
|
|
||||||
},
|
|
||||||
[`& .actions`]: {
|
|
||||||
padding: "0 1rem 1rem 1rem",
|
|
||||||
},
|
|
||||||
[`& .serverVersion`]: {
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
marginLeft: "0.5rem",
|
|
||||||
},
|
|
||||||
[`& .matrixVersions`]: {
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
marginBottom: "1rem",
|
|
||||||
marginLeft: "0.5rem",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const { restrictBaseUrl } = useAppContext();
|
const { restrictBaseUrl } = useAppContext();
|
||||||
const allowSingleBaseUrl = typeof restrictBaseUrl === "string";
|
const allowSingleBaseUrl = typeof restrictBaseUrl === "string";
|
||||||
const allowMultipleBaseUrls = (Array.isArray(restrictBaseUrl) && restrictBaseUrl.length > 0 && restrictBaseUrl[0] !== "" && restrictBaseUrl[0] !== null);
|
const allowMultipleBaseUrls =
|
||||||
|
Array.isArray(restrictBaseUrl) &&
|
||||||
|
restrictBaseUrl.length > 0 &&
|
||||||
|
restrictBaseUrl[0] !== "" &&
|
||||||
|
restrictBaseUrl[0] !== null;
|
||||||
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
|
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
|
@ -98,8 +49,13 @@ const LoginPage = () => {
|
||||||
const base_url = allowSingleBaseUrl ? restrictBaseUrl : storage.getItem("base_url");
|
const base_url = allowSingleBaseUrl ? restrictBaseUrl : storage.getItem("base_url");
|
||||||
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||||
|
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loginToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (loginToken) {
|
|
||||||
const ssoToken = loginToken[1];
|
const ssoToken = loginToken[1];
|
||||||
console.log("SSO token is", ssoToken);
|
console.log("SSO token is", ssoToken);
|
||||||
// Prevent further requests
|
// Prevent further requests
|
||||||
|
@ -127,7 +83,7 @@ const LoginPage = () => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}, [loginToken]);
|
||||||
|
|
||||||
const validateBaseUrl = value => {
|
const validateBaseUrl = value => {
|
||||||
if (!value.match(/^(http|https):\/\//)) {
|
if (!value.match(/^(http|https):\/\//)) {
|
||||||
|
@ -212,6 +168,18 @@ const LoginPage = () => {
|
||||||
}, [formData.base_url, form]);
|
}, [formData.base_url, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
value={loginMethod}
|
||||||
|
onChange={(_, newValue) => setLoginMethod(newValue as LoginMethod)}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Tab label={translate("synapseadmin.auth.credentials")} value="credentials" />
|
||||||
|
<Tab label={translate("synapseadmin.auth.access_token")} value="accessToken" />
|
||||||
|
</Tabs>
|
||||||
|
{loginMethod === "credentials" ? (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -236,6 +204,18 @@ const LoginPage = () => {
|
||||||
validate={required()}
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<TextInput
|
||||||
|
source="accessToken"
|
||||||
|
label="synapseadmin.auth.access_token"
|
||||||
|
disabled={loading}
|
||||||
|
resettable
|
||||||
|
validate={required()}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<Box>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
source="base_url"
|
source="base_url"
|
||||||
|
@ -263,7 +243,7 @@ const LoginPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form defaultValues={{ base_url: base_url }} onSubmit={handleSubmit} mode="onTouched">
|
<Form defaultValues={{ base_url: base_url }} onSubmit={handleSubmit} mode="onTouched">
|
||||||
<FormBox>
|
<LoginFormBox>
|
||||||
<Card className="card">
|
<Card className="card">
|
||||||
<Box className="avatar">
|
<Box className="avatar">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
@ -312,7 +292,7 @@ const LoginPage = () => {
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
</FormBox>
|
</LoginFormBox>
|
||||||
<Notification />
|
<Notification />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,13 +23,13 @@ describe("authProvider", () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const ret: undefined = await authProvider.login({
|
const ret = await authProvider.login({
|
||||||
base_url: "http://example.com",
|
base_url: "http://example.com",
|
||||||
username: "@user:example.com",
|
username: "@user:example.com",
|
||||||
password: "secret",
|
password: "secret",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(ret).toBe(undefined);
|
expect(ret).toEqual({redirectTo: "/"});
|
||||||
expect(fetch).toBeCalledWith("http://example.com/_matrix/client/r0/login", {
|
expect(fetch).toBeCalledWith("http://example.com/_matrix/client/r0/login", {
|
||||||
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","identifier":{"type":"m.id.user","user":"@user:example.com"},"password":"secret"}',
|
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","identifier":{"type":"m.id.user","user":"@user:example.com"},"password":"secret"}',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
|
@ -55,12 +55,12 @@ describe("authProvider", () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const ret: undefined = await authProvider.login({
|
const ret = await authProvider.login({
|
||||||
base_url: "https://example.com/",
|
base_url: "https://example.com/",
|
||||||
loginToken: "login_token",
|
loginToken: "login_token",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(ret).toBe(undefined);
|
expect(ret).toEqual({redirectTo: "/"});
|
||||||
expect(fetch).toHaveBeenCalledWith("https://example.com/_matrix/client/r0/login", {
|
expect(fetch).toHaveBeenCalledWith("https://example.com/_matrix/client/r0/login", {
|
||||||
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}',
|
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
|
|
|
@ -10,14 +10,16 @@ const authProvider: AuthProvider = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
loginToken,
|
loginToken,
|
||||||
|
accessToken,
|
||||||
}: {
|
}: {
|
||||||
base_url: string;
|
base_url: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
loginToken: string;
|
loginToken: string;
|
||||||
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
console.log("login ");
|
console.log("login ");
|
||||||
const options: Options = {
|
let options: Options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(
|
body: JSON.stringify(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
|
@ -55,11 +57,30 @@ const authProvider: AuthProvider = {
|
||||||
storage.setItem("base_url", base_url);
|
storage.setItem("base_url", base_url);
|
||||||
|
|
||||||
const decoded_base_url = window.decodeURIComponent(base_url);
|
const decoded_base_url = window.decodeURIComponent(base_url);
|
||||||
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
|
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/r0/login");
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
try {
|
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);
|
response = await fetchUtils.fetchJson(login_api_url, options);
|
||||||
|
const json = response.json;
|
||||||
|
storage.setItem("home_server", accessToken ? base_url : json.home_server);
|
||||||
|
storage.setItem("user_id", json.user_id);
|
||||||
|
storage.setItem("access_token", accessToken ? accessToken : json.access_token);
|
||||||
|
storage.setItem("device_id", json.device_id);
|
||||||
|
storage.setItem("login_type", accessToken ? "accessToken" : "credentials");
|
||||||
|
|
||||||
|
return Promise.resolve({redirectTo: "/"});
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
const error = err as HttpError;
|
const error = err as HttpError;
|
||||||
const errorStatus = error.status;
|
const errorStatus = error.status;
|
||||||
|
@ -73,12 +94,6 @@ const authProvider: AuthProvider = {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = response.json;
|
|
||||||
storage.setItem("home_server", json.home_server);
|
|
||||||
storage.setItem("user_id", json.user_id);
|
|
||||||
storage.setItem("access_token", json.access_token);
|
|
||||||
storage.setItem("device_id", json.device_id);
|
|
||||||
},
|
},
|
||||||
// called when the user clicks on the logout button
|
// called when the user clicks on the logout button
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
|
@ -102,6 +117,7 @@ const authProvider: AuthProvider = {
|
||||||
console.log("Error logging out", err);
|
console.log("Error logging out", err);
|
||||||
} finally {
|
} finally {
|
||||||
storage.removeItem("access_token");
|
storage.removeItem("access_token");
|
||||||
|
storage.removeItem("login_type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue