Compare commits

..

8 Commits

Author SHA1 Message Date
c634d4a891 feat: add some css style for the custom Provider button (#1185)
* fix: add some css style for the custom button

* fix: refactor previous code

* fix: add i18 adaptation

* fix: modifiy the saml codition
2022-10-06 19:28:02 +08:00
3dc01ec85d fix: language widget poisition without border css (#1188) 2022-10-06 17:26:12 +08:00
a7324f1da1 Improve className 2022-10-03 22:45:36 +08:00
6da452d7e0 feat: show language widget in signup and signin pages (#1180) 2022-10-03 22:40:19 +08:00
5abcf913e6 Fix language menu 2022-10-03 22:39:10 +08:00
58455e688e Improve WebAuthnCredentialTable and border radius 2022-10-03 18:46:40 +08:00
4d6f68eddc Improve footer and color 2022-10-03 17:43:19 +08:00
67f3c5a489 Add verificationCode to login method 2022-10-03 15:41:20 +08:00
18 changed files with 185 additions and 146 deletions

View File

@ -16,6 +16,7 @@ package controllers
import (
"bytes"
"fmt"
"io"
"github.com/casdoor/casdoor/object"
@ -100,7 +101,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError("Please Giveout Owner and Username.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", userOwner, userName))
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)

View File

@ -43,7 +43,7 @@ module.exports = {
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {"@primary-color": "rgb(45,120,213)"},
modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"},
javascriptEnabled: true,
},
},

View File

@ -610,7 +610,7 @@ class App extends Component {
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", width: "80%", position: "absolute", left: "145px"}}
style={{lineHeight: "64px", width: "78%", position: "absolute", left: "145px"}}
>
{
this.renderMenu()
@ -682,7 +682,7 @@ class App extends Component {
textAlign: "center",
}
}>
Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a>
</Footer>
</>
);

View File

@ -59,11 +59,18 @@
height: 70px; /* Footer height */
}
.language_box {
#language-box-corner {
position: absolute;
top: 75px;
right: 0;
}
.language-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center;
background-repeat: no-repeat;
border-radius: 5px;
width: 45px;
height: 65px;
float: right;
@ -87,9 +94,19 @@
align-items: stretch;
}
.login-content {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
margin: 0 auto;
position: relative;
}
.loginBackground {
height: 100%;
background: #ffffff no-repeat;
background: #fff no-repeat;
background-size: 100% 100%;
background-attachment: fixed;
}

View File

@ -37,8 +37,8 @@ class SelectLanguageBox extends React.Component {
Setting.changeLanguage(e.key);
}}>
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
@ -49,7 +49,7 @@ class SelectLanguageBox extends React.Component {
return (
<Dropdown overlay={menu} >
<div className="language_box" />
<div className="language-box" id={this.props.id} style={this.props.style} />
</Dropdown>
);
}

View File

@ -19,40 +19,6 @@ import * as UserWebauthnBackend from "./backend/UserWebauthnBackend";
import * as Setting from "./Setting";
class WebAuthnCredentialTable extends React.Component {
render() {
const columns = [
{
title: i18next.t("user:WebAuthn credentials"),
dataIndex: "ID",
key: "ID",
},
{
title: i18next.t("general:Action"),
key: "action",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);
},
},
];
return (
<Table scroll={{x: "max-content"}} rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
title={() => (
<div>
{i18next.t("user:WebAuthn credentials")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button disabled={!this.props.isSelf} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
{i18next.t("general:Add")}
</Button>
</div>
)}
/>
);
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.props.updateTable(table);
@ -71,6 +37,41 @@ class WebAuthnCredentialTable extends React.Component {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
});
}
render() {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "ID",
key: "ID",
},
{
title: i18next.t("general:Action"),
key: "action",
width: "170px",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);
},
},
];
return (
<Table rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
title={() => (
<div>
{i18next.t("user:WebAuthn credentials")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button disabled={!this.props.isSelf} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
{i18next.t("general:Add")}
</Button>
</div>
)}
/>
);
}
}
export default WebAuthnCredentialTable;

View File

@ -28,6 +28,7 @@ import SelfLoginButton from "./SelfLoginButton";
import i18next from "i18next";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
const {TabPane} = Tabs;
@ -41,7 +42,6 @@ class LoginPage extends React.Component {
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
application: null,
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
isCodeSignin: false,
msg: null,
username: null,
validEmailOrPhone: false,
@ -349,7 +349,7 @@ class LoginPage extends React.Component {
},
{
validator: (_, value) => {
if (this.state.isCodeSignin) {
if (this.state.loginMethod === "verificationCode") {
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
@ -372,7 +372,7 @@ class LoginPage extends React.Component {
<Input
id = "input"
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={this.state.isCodeSignin ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
disabled={!application.enablePassword}
onChange={e => {
this.setState({
@ -399,29 +399,17 @@ class LoginPage extends React.Component {
</a>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{
this.state.loginMethod === "password" ?
(
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign In")}
</Button>
) :
(
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign in with WebAuthn")}
</Button>
)
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
i18next.t("login:Sign In")
}
</Button>
{
this.renderFooter(application)
}
@ -479,19 +467,6 @@ class LoginPage extends React.Component {
} else {
return (
<React.Fragment>
<span style={{float: "left"}}>
{
!application.enableCodeSignin ? null : (
<a onClick={() => {
this.setState({
isCodeSignin: !this.state.isCodeSignin,
});
}}>
{this.state.isCodeSignin ? i18next.t("login:Sign in with password") : i18next.t("login:Sign in with code")}
</a>
)
}
</span>
<span style={{float: "right"}}>
{
!application.enableSignUp ? null : (
@ -639,7 +614,23 @@ class LoginPage extends React.Component {
renderPasswordOrCodeInput() {
const application = this.getApplicationObj();
if (this.state.loginMethod === "password") {
return this.state.isCodeSignin ? (
return (
<Col span={24}>
<Form.Item
name="password"
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
>
<Input.Password
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={i18next.t("login:Password")}
disabled={!application.enablePassword}
/>
</Form.Item>
</Col>
);
} else if (this.state.loginMethod === "verificationCode") {
return (
<Col span={24}>
<Form.Item
name="code"
@ -652,32 +643,29 @@ class LoginPage extends React.Component {
/>
</Form.Item>
</Col>
) : (
<Col span={24}>
<Form.Item
name="password"
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={i18next.t("login:Password")}
disabled={!application.enablePassword}
/>
</Form.Item>
</Col>
);
} else {
return null;
}
}
renderMethodChoiceBox() {
const application = this.getApplicationObj();
if (application.enableWebAuthn) {
if (application.enableCodeSignin || application.enableWebAuthn) {
return (
<div>
<Tabs defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
<Tabs size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
<TabPane tab={i18next.t("login:Password")} key="password" />
<TabPane tab={"WebAuthn"} key="webAuthn" />
{
!application.enableCodeSignin ? null : (
<TabPane tab={i18next.t("login:Verification Code")} key="verificationCode" />
)
}
{
!application.enableWebAuthn ? null : (
<TabPane tab={i18next.t("login:WebAuthn")} key="webAuthn" />
)
}
</Tabs>
</div>
);
@ -713,7 +701,9 @@ class LoginPage extends React.Component {
<CustomGithubCorner />
<Row>
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}>
<div className="login-content">
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}>
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "80px" : "45px", right: formStyle !== null ? "5px" : "-45px"}} />
<div>
{
Setting.renderHelmet(application)
@ -731,7 +721,7 @@ class LoginPage extends React.Component {
this.renderForm(application)
}
</div>
</div>
</div>
</Col>
</Row>

View File

@ -129,6 +129,32 @@ export function renderProviderLogo(provider, application, width, margin, size, l
);
}
} else if (provider.type === "Custom") {
// style definition
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
const customAStyle = {display: "block", height: "55px", color: "#000"};
const customButtonStyle = {display: "flex", alignItems: "center", width: "calc(100% - 10px)", height: "50px", margin: "5px", padding: "0 10px", backgroundColor: "transparent", boxShadow: "0px 1px 3px rgba(0,0,0,0.5)", border: "0px", borderRadius: "3px", cursor: "pointer"};
const customImgStyle = {justfyContent: "space-between"};
const customSpanStyle = {textAlign: "center", lineHeight: "50px", width: "100%", fontSize: "19px"};
if (provider.category === "OAuth") {
return (
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "signup")} style={customAStyle}>
<button style={customButtonStyle}>
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
<span style={customSpanStyle}>{text}</span>
</button>
</a>
);
} else if (provider.category === "SAML") {
return (
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)} style={customAStyle}>
<button style={customButtonStyle}>
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
<span style={customSpanStyle}>{text}</span>
</button>
</a>
);
}
} else {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>

View File

@ -25,6 +25,7 @@ import * as ApplicationBackend from "../backend/ApplicationBackend";
import {CountDownInput} from "../common/CountDownInput";
import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner";
import SelectLanguageBox from "../SelectLanguageBox";
const formItemLayout = {
labelCol: {
@ -622,7 +623,9 @@ class SignupPage extends React.Component {
&nbsp;
<Row>
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} >
<div className="login-content">
<div style={{marginBottom: "10px", textAlign: "center", ...formStyle}}>
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "3px" : "-20px", right: formStyle !== null ? "5px" : "-45px"}} />
{
Setting.renderHelmet(application)
}
@ -633,6 +636,7 @@ class SignupPage extends React.Component {
this.renderForm(application)
}
</div>
</div>
</Col>
</Row>
{

View File

@ -87,7 +87,7 @@ export const CaptchaPreview = ({
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "3px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}

View File

@ -101,7 +101,7 @@ export const CountDownInput = (props) => {
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "3px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
"Sign In": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Mit Code anmelden",
"Sign in with password": "Mit Passwort anmelden",
"Sign in with {type}": "Mit {type} anmelden",
"Signing in...": "Anmelden...",
"The input is not valid Email or Phone!": "Die Eingabe ist keine gültige E-Mail oder Telefon!",
"To access": "Zu Zugriff",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "jetzt anmelden",
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Sign in with code",
"Sign in with password": "Sign in with password",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
"To access": "To access",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "sign up now",
"username, Email or phone": "username, Email or phone"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
"Sign In": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Se connecter avec le code",
"Sign in with password": "Se connecter avec le mot de passe",
"Sign in with {type}": "Se connecter avec {type}",
"Signing in...": "Connexion en cours...",
"The input is not valid Email or Phone!": "L'entrée n'est pas valide Email ou Téléphone !",
"To access": "Pour accéder à",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "inscrivez-vous maintenant",
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
"Sign In": "サインイン",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "コードでサインイン",
"Sign in with password": "パスワードでサインイン",
"Sign in with {type}": "{type} でサインイン",
"Signing in...": "サインイン中...",
"The input is not valid Email or Phone!": "入力されたメールアドレスまたは電話番号が正しくありません。",
"To access": "アクセスするには",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "今すぐサインアップ",
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Sign in with code",
"Sign in with password": "Sign in with password",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
"To access": "To access",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "sign up now",
"username, Email or phone": "username, Email or phone"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
"Sign In": "Войти",
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with code": "Войти с помощью кода",
"Sign in with password": "Войти с помощью пароля",
"Sign in with {type}": "Войти с помощью {type}",
"Signing in...": "Вход...",
"The input is not valid Email or Phone!": "Введен неверный адрес электронной почты или телефон!",
"To access": "На доступ",
"Verification Code": "Verification Code",
"WebAuthn": "WebAuthn",
"sign up now": "зарегистрироваться",
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
},

View File

@ -277,12 +277,12 @@
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录",
"Sign in with code": "验证码登录",
"Sign in with password": "密码登录",
"Sign in with {type}": "{type}登录",
"Signing in...": "正在登录...",
"The input is not valid Email or Phone!": "您输入的电子邮箱格式或手机号有误!",
"To access": "访问",
"Verification Code": "验证码",
"WebAuthn": "Web身份验证",
"sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号"
},
@ -702,7 +702,7 @@
"Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx",
"Upload a photo": "上传头像",
"WebAuthn credentials": "WebAuthn credentials",
"WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码"
},
"webhook": {