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:
Nya Candy 2022-02-18 03:24:46 +08:00 committed by GitHub
parent 38541b8f02
commit c891afa611
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 11 deletions

View file

@ -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 />

View file

@ -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.",

View file

@ -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.",

View file

@ -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: {

View file

@ -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(
type: "m.login.password", Object.assign(
user: username, {
password: password, device_id: localStorage.getItem("device_id"),
device_id: localStorage.getItem("device_id"), initial_device_display_name: "Synapse Admin",
initial_device_display_name: "Synapse Admin", },
}), loginToken
? {
type: "m.login.token",
token: loginToken,
}
: {
type: "m.login.password",
user: username,
password: password,
}
)
),
}; };
// 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