mirror of
https://github.com/etkecc/synapse-admin.git
synced 2024-11-29 11:59:10 +03:00
feat: support SSO login (#196)
* feat: support SSO login * fix: lint * fix: add back homeserver force protection * fix: add back login notice * fix: simplify login options
This commit is contained in:
parent
38541b8f02
commit
c891afa611
5 changed files with 107 additions and 11 deletions
|
@ -78,11 +78,48 @@ const LoginPage = ({ theme }) => {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
var locale = useLocale();
|
var locale = useLocale();
|
||||||
const setLocale = useSetLocale();
|
const setLocale = useSetLocale();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = localStorage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const cfg_base_url = process.env.REACT_APP_SERVER;
|
const cfg_base_url = process.env.REACT_APP_SERVER;
|
||||||
|
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
|
||||||
|
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
|
||||||
|
|
||||||
|
if (loginToken) {
|
||||||
|
const ssoToken = loginToken[1];
|
||||||
|
console.log("SSO token is", ssoToken);
|
||||||
|
// Prevent further requests
|
||||||
|
window.history.replaceState(
|
||||||
|
{},
|
||||||
|
"",
|
||||||
|
window.location.href.replace(loginToken[0], "#").split("#")[0]
|
||||||
|
);
|
||||||
|
const baseUrl = localStorage.getItem("sso_base_url");
|
||||||
|
localStorage.removeItem("sso_base_url");
|
||||||
|
if (baseUrl) {
|
||||||
|
const auth = {
|
||||||
|
base_url: baseUrl,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
loginToken: ssoToken,
|
||||||
|
};
|
||||||
|
console.log("Base URL is:", baseUrl);
|
||||||
|
console.log("SSO Token is:", ssoToken);
|
||||||
|
console.log("Let's try token login...");
|
||||||
|
login(auth).catch(error => {
|
||||||
|
alert(
|
||||||
|
typeof error === "string"
|
||||||
|
? error
|
||||||
|
: typeof error === "undefined" || !error.message
|
||||||
|
? "ra.auth.sign_in_error"
|
||||||
|
: error.message
|
||||||
|
);
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const renderInput = ({
|
const renderInput = ({
|
||||||
meta: { touched, error } = {},
|
meta: { touched, error } = {},
|
||||||
|
@ -137,6 +174,14 @@ const LoginPage = ({ theme }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSSO = () => {
|
||||||
|
localStorage.setItem("sso_base_url", ssoBaseUrl);
|
||||||
|
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
|
||||||
|
window.location.href
|
||||||
|
)}`;
|
||||||
|
window.location.href = ssoFullUrl;
|
||||||
|
};
|
||||||
|
|
||||||
const extractHomeServer = username => {
|
const extractHomeServer = username => {
|
||||||
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
|
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
|
||||||
if (!username) return null;
|
if (!username) return null;
|
||||||
|
@ -188,6 +233,31 @@ const LoginPage = ({ theme }) => {
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
setServerVersion("");
|
setServerVersion("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set SSO Url
|
||||||
|
const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`;
|
||||||
|
let supportPass = false,
|
||||||
|
supportSSO = false;
|
||||||
|
fetchUtils
|
||||||
|
.fetchJson(authMethodUrl, { method: "GET" })
|
||||||
|
.then(({ json }) => {
|
||||||
|
json.flows.forEach(f => {
|
||||||
|
if (f.type === "m.login.password") {
|
||||||
|
supportPass = true;
|
||||||
|
} else if (f.type === "m.login.sso") {
|
||||||
|
supportSSO = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSupportPassAuth(supportPass);
|
||||||
|
if (supportSSO) {
|
||||||
|
setSSOBaseUrl(formData.base_url);
|
||||||
|
} else {
|
||||||
|
setSSOBaseUrl("");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
setSSOBaseUrl("");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[formData.base_url]
|
[formData.base_url]
|
||||||
);
|
);
|
||||||
|
@ -200,7 +270,7 @@ const LoginPage = ({ theme }) => {
|
||||||
name="username"
|
name="username"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("ra.auth.username")}
|
label={translate("ra.auth.username")}
|
||||||
disabled={loading}
|
disabled={loading || !supportPassAuth}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -212,7 +282,7 @@ const LoginPage = ({ theme }) => {
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("ra.auth.password")}
|
label={translate("ra.auth.password")}
|
||||||
type="password"
|
type="password"
|
||||||
disabled={loading}
|
disabled={loading || !supportPassAuth}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
@ -273,13 +343,24 @@ const LoginPage = ({ theme }) => {
|
||||||
variant="contained"
|
variant="contained"
|
||||||
type="submit"
|
type="submit"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={loading}
|
disabled={loading || !supportPassAuth}
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{loading && <CircularProgress size={25} thickness={2} />}
|
{loading && <CircularProgress size={25} thickness={2} />}
|
||||||
{translate("ra.auth.sign_in")}
|
{translate("ra.auth.sign_in")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={handleSSO}
|
||||||
|
disabled={loading || ssoBaseUrl === ""}
|
||||||
|
className={classes.button}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{loading && <CircularProgress size={25} thickness={2} />}
|
||||||
|
{translate("synapseadmin.auth.sso_sign_in")}
|
||||||
|
</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
<Notification />
|
<Notification />
|
||||||
|
|
|
@ -10,6 +10,7 @@ const de = {
|
||||||
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
|
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
|
||||||
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",
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
|
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
|
||||||
|
|
|
@ -10,6 +10,7 @@ const en = {
|
||||||
username_error: "Please enter fully qualified user ID: '@user:domain'",
|
username_error: "Please enter fully qualified user ID: '@user:domain'",
|
||||||
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",
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
|
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
|
||||||
|
|
|
@ -10,10 +10,12 @@ const zh = {
|
||||||
username_error: "请输入完整有效的用户 ID: '@user:domain'",
|
username_error: "请输入完整有效的用户 ID: '@user:domain'",
|
||||||
protocol_error: "URL 需要以'http://'或'https://'作为起始",
|
protocol_error: "URL 需要以'http://'或'https://'作为起始",
|
||||||
url_error: "不是一个有效的 Matrix 服务器地址",
|
url_error: "不是一个有效的 Matrix 服务器地址",
|
||||||
|
sso_sign_in: "使用 SSO 登录",
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id:
|
invalid_user_id:
|
||||||
"必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
|
"必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
|
||||||
|
tabs: { sso: "SSO" },
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
tabs: {
|
tabs: {
|
||||||
|
|
|
@ -2,20 +2,31 @@ import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
const authProvider = {
|
const authProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
login: ({ base_url, username, password }) => {
|
login: ({ base_url, username, password, loginToken }) => {
|
||||||
// force homeserver for protection in case the form is manipulated
|
// force homeserver for protection in case the form is manipulated
|
||||||
base_url = process.env.REACT_APP_SERVER || base_url;
|
base_url = process.env.REACT_APP_SERVER || base_url;
|
||||||
|
|
||||||
console.log("login ");
|
console.log("login ");
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
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",
|
type: "m.login.password",
|
||||||
user: username,
|
user: username,
|
||||||
password: password,
|
password: password,
|
||||||
device_id: localStorage.getItem("device_id"),
|
}
|
||||||
initial_device_display_name: "Synapse Admin",
|
)
|
||||||
}),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// use the base_url from login instead of the well_known entry from the
|
// use the base_url from login instead of the well_known entry from the
|
||||||
|
|
Loading…
Reference in a new issue