Compare commits

...

6 Commits

Author SHA1 Message Date
xAmast
25ee4226d3 feat: clear the session of a signin but non-existent user (#1246) 2022-10-29 20:18:02 +08:00
Bingchang Chen
9d5b019243 fix: nil error if init data is empty (#1247) 2022-10-29 20:04:43 +08:00
Mr Forest
6bb7b545b4 feat: restrict DingTalk user log in who is under the DingTalk Org(which ClientId belong) (#1241)
* feat: fix bug in GetAcceptLanguage()

* feat: add appName when logging in with DingTalk

* fix review problems

* format code

* delete useless printf

* modify display name

Co-authored-by: Gucheng Wang <nomeguy@qq.com>
2022-10-28 22:14:05 +08:00
wenxuan70
25d56ee8d5 feat: allow captcha to be enabled when logging in (#1211)
* Fix bug in GetAcceptLanguage()

* feat: allow captcha to be enabled when logging in

* feat: when the login password is wrong, enable captcha

* feat: Restrict captcha from frontend

* fix: modify CaptchaModal component

* fix: modify the words of i18n

* Update data.json

Co-authored-by: Gucheng Wang <nomeguy@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-28 13:38:14 +08:00
Chell
7e5952c804 fix: login / signin frontend router (#1244)
* fix: go to link

* fix: remove gotologin

* fix: redirect to login page

* fix: redirect to login page

* remove comments

* fix: formats

* fix: formats

* Update Setting.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-28 02:23:57 +08:00
Yaodong Yu
80bf29d79a feat: fix showing wrong error message: "Please sign in first" (#1245) 2022-10-27 23:50:45 +08:00
37 changed files with 530 additions and 177 deletions

View File

@@ -14,6 +14,8 @@
package captcha
import "fmt"
type CaptchaProvider interface {
VerifyCaptcha(token, clientSecret string) (bool, error)
}
@@ -32,3 +34,12 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
}
return nil
}
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
provider := GetCaptchaProvider(captchaType)
if provider == nil {
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
}
return provider.VerifyCaptcha(token, clientSecret)
}

View File

@@ -64,6 +64,10 @@ type RequestForm struct {
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
CaptchaType string `json:"captchaType"`
CaptchaToken string `json:"captchaToken"`
ClientSecret string `json:"clientSecret"`
}
type Response struct {
@@ -241,8 +245,7 @@ func (c *ApiController) Logout() {
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ClearUserSession()
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)

View File

@@ -23,6 +23,8 @@ import (
"strings"
"time"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
@@ -251,6 +253,25 @@ func (c *ApiController) Login() {
return
}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
if object.CheckToEnableCaptcha(application) {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
if err != nil {
c.ResponseError(err.Error())
return
}
if !isHuman {
c.ResponseError("Turing test failed.")
return
}
}
password := form.Password
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
}

View File

@@ -63,8 +63,7 @@ func (c *ApiController) GetSessionUsername() string {
if sessionData != nil &&
sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ClearUserSession()
return ""
}
@@ -85,13 +84,17 @@ func (c *ApiController) GetSessionApplication() *object.Application {
return application
}
func (c *ApiController) ClearUserSession() {
c.SetSessionUsername("")
c.SetSessionData(nil)
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&
sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ClearUserSession()
return "", ""
}
scopeValue := c.GetSession("scope")

View File

@@ -83,7 +83,7 @@ func (c *ApiController) SetTokenErrorHttpStatus() {
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("LoginErr.SignInFirst"))
c.ResponseError(c.T("LoginErr.LoginFirst"), "Please login first")
return "", false
}
return userId, true
@@ -98,6 +98,7 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user := object.GetUser(userId)
if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId))
return nil, false
}

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = 提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定:
ProviderCanNotSignUp = 提供商账户: %s 与用户名: %s (%s) 不存在且 不允许通过 %s 注册新账户, 请使用其他方式注册
SignOutFirst = 请在登录前登出
SessionOutdated = Session已过期请重新登陆
SignInFirst = 请先登出
UserDoNotExist = 用户不存在: %s/%s
UserIsForbidden = 该用户被禁止登陆,请联系管理员
UnknownAuthentication = 未知的认证类型 (非密码或提供商认证), form = %s

View File

@@ -15,9 +15,12 @@
package idp
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
@@ -167,7 +170,10 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
Email: dtUserInfo.Email,
AvatarUrl: dtUserInfo.AvatarUrl,
}
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
if !isUserInOrg {
return nil, err
}
return &userInfo, nil
}
@@ -194,3 +200,62 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
return data, nil
}
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
appKey := idp.Config.ClientID
appSecret := idp.Config.ClientSecret
body := make(map[string]string)
body["appKey"] = appKey
body["appSecret"] = appSecret
bodyData, err := json.Marshal(body)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err.Error())
}
var data struct {
ExpireIn int `json:"expireIn"`
AccessToken string `json:"accessToken"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
log.Println(err.Error())
}
return data.AccessToken
}
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
body := make(map[string]string)
body["unionid"] = unionId
bodyData, err := json.Marshal(body)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
accessToken := idp.getInnerAppAccessToken()
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err.Error())
}
var data struct {
ErrCode int `json:"errcode"`
ErrMessage string `json:"errmsg"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
log.Println(err.Error())
}
if data.ErrCode == 60121 {
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
}
return true, nil
}

View File

@@ -340,3 +340,20 @@ func CheckUsername(username string, lang string) string {
return ""
}
func CheckToEnableCaptcha(application *Application) bool {
if len(application.Providers) == 0 {
return false
}
for _, providerItem := range application.Providers {
if providerItem.Provider == nil {
continue
}
if providerItem.Provider.Category == "Captcha" && providerItem.Provider.Type == "Default" {
return providerItem.Rule == "Always"
}
}
return false
}

View File

@@ -143,7 +143,7 @@ func initBuiltInApplication() {
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},

View File

@@ -56,12 +56,40 @@ func readInitDataFromFile(filePath string) *InitData {
s := util.ReadStringFromPath(filePath)
data := &InitData{}
data := &InitData{
Organizations: []*Organization{},
Applications: []*Application{},
Users: []*User{},
Certs: []*Cert{},
Providers: []*Provider{},
Ldaps: []*Ldap{},
}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
// transform nil slice to empty slice
for _, organization := range data.Organizations {
if organization.Tags == nil {
organization.Tags = []string{}
}
}
for _, application := range data.Applications {
if application.Providers == nil {
application.Providers = []*ProviderItem{}
}
if application.SignupItems == nil {
application.SignupItems = []*SignupItem{}
}
if application.GrantTypes == nil {
application.GrantTypes = []string{}
}
if application.RedirectUris == nil {
application.RedirectUris = []string{}
}
}
return data
}

View File

@@ -21,6 +21,7 @@ type ProviderItem struct {
CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"`
AlertType string `json:"alertType"`
Rule string `json:"rule"`
Provider *Provider `json:"provider"`
}

View File

@@ -221,10 +221,9 @@ class App extends Component {
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
this.setLanguage(account);
} else {
if (res.msg !== "Please sign in first") {
if (res.data !== "Please login first") {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
}
}

View File

@@ -39,7 +39,7 @@ class ProviderTable extends React.Component {
}
addRow(table) {
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None"};
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None", rule: "None"};
if (table === undefined) {
table = [];
}
@@ -193,6 +193,28 @@ class ProviderTable extends React.Component {
// )
// }
// },
{
title: i18next.t("application:Rule"),
dataIndex: "rule",
key: "rule",
width: "100px",
render: (text, record, index) => {
if (record.provider?.category !== "Captcha") {
return null;
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
defaultValue="None"
onChange={value => {
this.updateField(table, index, "rule", value);
}} >
<Option key="None" value="None">{i18next.t("application:None")}</Option>
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
</Select>
);
},
},
{
title: i18next.t("general:Action"),
key: "action",

View File

@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link, useHistory} from "react-router-dom";
import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n";
import i18next from "i18next";
@@ -22,7 +23,6 @@ import copy from "copy-to-clipboard";
import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet";
import * as Conf from "./Conf";
import {Link} from "react-router-dom";
import * as path from "path-browserify";
export const ServerUrl = "";
@@ -743,26 +743,31 @@ export function renderLogo(application) {
}
}
export function goToLogin(ths, application) {
export function getLoginLink(application) {
let url;
if (application === null) {
return;
}
if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
goToLink(link);
return;
}
if (authConfig.appName === application.name) {
goToLinkSoft(ths, "/login");
url = null;
} else if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
url = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
} else if (authConfig.appName === application.name) {
url = "/login";
} else if (application.signinUrl === "") {
url = path.join(application.homepageUrl, "login");
} else {
if (application.signinUrl === "") {
goToLink(path.join(application.homepageUrl, "login"));
} else {
goToLink(application.signinUrl);
}
url = application.signinUrl;
}
return url;
}
export function renderLoginLink(application, text) {
const url = getLoginLink(application);
return renderLink(url, text, null);
}
export function redirectToLoginPage(application) {
const loginLink = getLoginLink(application);
const history = useHistory();
history.push(loginLink);
}
function renderLink(url, text, onClick) {

View File

@@ -164,7 +164,7 @@ class SignupTable extends React.Component {
},
},
{
title: i18next.t("application:rule"),
title: i18next.t("application:Rule"),
dataIndex: "rule",
key: "rule",
width: "155px",

View File

@@ -166,7 +166,7 @@ class ForgetPage extends React.Component {
values.userOwner = this.state.application?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") {
Setting.goToLogin(this, this.state.application);
Setting.redirectToLoginPage(this.state.application);
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}

View File

@@ -13,7 +13,6 @@
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
import {LockOutlined, UserOutlined} from "@ant-design/icons";
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
@@ -30,6 +29,7 @@ import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next";
import {CaptchaModal} from "../common/CaptchaModal";
const {TabPane} = Tabs;
@@ -49,6 +49,9 @@ class LoginPage extends React.Component {
validEmail: false,
validPhone: false,
loginMethod: "password",
enableCaptchaModal: false,
openCaptchaModal: false,
verifyCaptcha: undefined,
};
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@@ -69,6 +72,18 @@ class LoginPage extends React.Component {
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.application && !prevState.application) {
const defaultCaptchaProviderItems = this.getDefaultCaptchaProviderItems(this.state.application);
if (!defaultCaptchaProviderItems) {
return;
}
this.setState({enableCaptchaModal: defaultCaptchaProviderItems.some(providerItem => providerItem.rule === "Always")});
}
}
getApplicationLogin() {
const oAuthParams = Util.getOAuthGetParameters();
AuthBackend.getApplicationLogin(oAuthParams)
@@ -226,6 +241,23 @@ class LoginPage extends React.Component {
return;
}
if (this.state.loginMethod === "password" && this.state.enableCaptchaModal) {
this.setState({
openCaptchaModal: true,
verifyCaptcha: (captchaType, captchaToken, secret) => {
values["captchaType"] = captchaType;
values["captchaToken"] = captchaToken;
values["clientSecret"] = secret;
this.login(values);
},
});
} else {
this.login(values);
}
}
login(values) {
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
if (this.state.type === "cas") {
// CAS
@@ -240,6 +272,8 @@ class LoginPage extends React.Component {
}
Util.showMessage("success", msg);
this.setState({openCaptchaModal: false});
if (casParams.service !== "") {
const st = res.data;
const newUrl = new URL(casParams.service);
@@ -247,6 +281,7 @@ class LoginPage extends React.Component {
window.location.href = newUrl.toString();
}
} else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
@@ -259,6 +294,7 @@ class LoginPage extends React.Component {
.then((res) => {
if (res.status === "ok") {
const responseType = values["type"];
if (responseType === "login") {
Util.showMessage("success", "Logged in successfully");
@@ -276,6 +312,7 @@ class LoginPage extends React.Component {
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
} else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
@@ -302,13 +339,11 @@ class LoginPage extends React.Component {
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
extra={[
<Link key="login" onClick={() => {
Setting.goToLogin(this, application);
}}>
<Button type="primary" key="signin">
Sign In
</Button>
</Link>,
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
{
i18next.t("login:Sign In")
}
</Button>,
]}
>
</Result>
@@ -421,6 +456,9 @@ class LoginPage extends React.Component {
i18next.t("login:Sign In")
}
</Button>
{
this.renderCaptchaModal(application)
}
{
this.renderFooter(application)
}
@@ -463,16 +501,54 @@ class LoginPage extends React.Component {
}
}
getDefaultCaptchaProviderItems(application) {
const providers = application?.providers;
if (providers === undefined || providers === null) {
return null;
}
return providers.filter(providerItem => {
if (providerItem.provider === undefined || providerItem.provider === null) {
return false;
}
return providerItem.provider.category === "Captcha" && providerItem.provider.type === "Default";
});
}
renderCaptchaModal(application) {
if (!this.state.enableCaptchaModal) {
return null;
}
const provider = this.getDefaultCaptchaProviderItems(application)
.filter(providerItem => providerItem.rule === "Always")
.map(providerItem => providerItem.provider)[0];
return <CaptchaModal
owner={provider.owner}
name={provider.name}
captchaType={provider.type}
subType={provider.subType}
clientId={provider.clientId}
clientId2={provider.clientId2}
clientSecret={provider.clientSecret}
clientSecret2={provider.clientSecret2}
open={this.state.openCaptchaModal}
onOk={(captchaType, captchaToken, secret) => this.state.verifyCaptcha?.(captchaType, captchaToken, secret)}
canCancel={false}
/>;
}
renderFooter(application) {
if (this.state.mode === "signup") {
return (
<div style={{float: "right"}}>
{i18next.t("signup:Have account?")}&nbsp;
<Link onClick={() => {
Setting.goToLogin(this, application);
}}>
{i18next.t("signup:sign in now")}
</Link>
{
Setting.renderLoginLink(application, i18next.t("signup:sign in now"))
}
</div>
);
} else {

View File

@@ -190,7 +190,7 @@ class PromptPage extends React.Component {
if (redirectUrl !== "" && redirectUrl !== null) {
Setting.goToLink(redirectUrl);
} else {
Setting.goToLogin(this, this.getApplicationObj());
Setting.redirectToLoginPage(this.getApplicationObj());
}
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
@@ -234,10 +234,10 @@ class PromptPage extends React.Component {
title="Sign Up Error"
subTitle={"You are unexpected to see this prompt page"}
extra={[
<Button type="primary" key="signin" onClick={() => {
Setting.goToLogin(this, application);
}}>
Sign In
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
{
i18next.t("login:Sign In")
}
</Button>,
]}
>

View File

@@ -69,7 +69,7 @@ class ResultPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
} else {
Setting.goToLogin(this, application);
Setting.redirectToLoginPage(application);
}
}}>
{i18next.t("login:Sign In")}

View File

@@ -541,10 +541,10 @@ class SignupPage extends React.Component {
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
extra={[
<Button type="primary" key="signin" onClick={() => {
Setting.goToLogin(this, application);
}}>
Sign In
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
{
i18next.t("login:Sign In")
}
</Button>,
]}
>
@@ -600,7 +600,7 @@ class SignupPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
} else {
Setting.goToLogin(this, application);
Setting.redirectToLoginPage(application);
}
}}>
{i18next.t("signup:sign in now")}

View File

@@ -0,0 +1,159 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next";
import React, {useEffect} from "react";
import * as UserBackend from "../backend/UserBackend";
import {CaptchaWidget} from "./CaptchaWidget";
import {SafetyOutlined} from "@ant-design/icons";
export const CaptchaModal = ({
owner,
name,
captchaType,
subType,
clientId,
clientId2,
clientSecret,
clientSecret2,
open,
onOk,
onCancel,
canCancel,
}) => {
const [visible, setVisible] = React.useState(false);
const [captchaImg, setCaptchaImg] = React.useState("");
const [captchaToken, setCaptchaToken] = React.useState("");
const [secret, setSecret] = React.useState(clientSecret);
const [secret2, setSecret2] = React.useState(clientSecret2);
useEffect(() => {
setVisible(() => {
if (open) {
getCaptchaFromBackend();
} else {
cleanUp();
}
return open;
});
}, [open]);
const handleOk = () => {
onOk?.(captchaType, captchaToken, secret);
};
const handleCancel = () => {
onCancel?.();
};
const cleanUp = () => {
setCaptchaToken("");
};
const getCaptchaFromBackend = () => {
UserBackend.getCaptcha(owner, name, true).then((res) => {
if (captchaType === "Default") {
setSecret(res.captchaId);
setCaptchaImg(res.captchaImage);
} else {
setSecret(res.clientSecret);
setSecret2(res.clientSecret2);
}
});
};
const renderDefaultCaptcha = () => {
return (
<Col>
<Row
style={{
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}
/>
<Row>
<Input
autoFocus
value={captchaToken}
prefix={<SafetyOutlined />}
placeholder={i18next.t("general:Captcha")}
onPressEnter={handleOk}
onChange={(e) => setCaptchaToken(e.target.value)}
/>
</Row>
</Col>
);
};
const onSubmit = (token) => {
setCaptchaToken(token);
};
const renderCheck = () => {
if (captchaType === "Default") {
return renderDefaultCaptcha();
} else {
return (
<Col>
<Row>
<CaptchaWidget
captchaType={captchaType}
subType={subType}
siteKey={clientId}
clientSecret={secret}
onChange={onSubmit}
clientId2={clientId2}
clientSecret2={secret2}
/>
</Row>
</Col>
);
}
};
const renderFooter = () => {
if (canCancel) {
return [
<Button key="cancel" onClick={handleCancel}>{i18next.t("user:Cancel")}</Button>,
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
];
} else {
return [
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
];
}
};
return (
<React.Fragment>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
visible={visible}
width={348}
footer={renderFooter()}
>
{renderCheck()}
</Modal>
</React.Fragment>
);
};

View File

@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Input, Modal, Row} from "antd";
import {Button} from "antd";
import React from "react";
import i18next from "i18next";
import * as UserBackend from "../backend/UserBackend";
import {CaptchaModal} from "./CaptchaModal";
import * as ProviderBackend from "../backend/ProviderBackend";
import {SafetyOutlined} from "@ant-design/icons";
import {CaptchaWidget} from "./CaptchaWidget";
import * as UserBackend from "../backend/UserBackend";
export const CaptchaPreview = ({
provider,
@@ -33,37 +32,9 @@ export const CaptchaPreview = ({
clientId2,
clientSecret2,
}) => {
const [visible, setVisible] = React.useState(false);
const [captchaImg, setCaptchaImg] = React.useState("");
const [captchaToken, setCaptchaToken] = React.useState("");
const [secret, setSecret] = React.useState(clientSecret);
const [secret2, setSecret2] = React.useState(clientSecret2);
const handleOk = () => {
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
setCaptchaToken("");
setVisible(false);
});
};
const handleCancel = () => {
setVisible(false);
};
const getCaptchaFromBackend = () => {
UserBackend.getCaptcha(owner, name, true).then((res) => {
if (captchaType === "Default") {
setSecret(res.captchaId);
setCaptchaImg(res.captchaImage);
} else {
setSecret(res.clientSecret);
setSecret2(res.clientSecret2);
}
});
};
const [open, setOpen] = React.useState(false);
const clickPreview = () => {
setVisible(true);
provider.name = name;
provider.clientId = clientId;
provider.type = captchaType;
@@ -71,64 +42,10 @@ export const CaptchaPreview = ({
if (clientSecret !== "***") {
provider.clientSecret = clientSecret;
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
getCaptchaFromBackend();
setOpen(true);
});
} else {
getCaptchaFromBackend();
}
};
const renderDefaultCaptcha = () => {
return (
<Col>
<Row
style={{
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: 10,
}}
/>
<Row>
<Input
autoFocus
value={captchaToken}
prefix={<SafetyOutlined />}
placeholder={i18next.t("general:Captcha")}
onPressEnter={handleOk}
onChange={(e) => setCaptchaToken(e.target.value)}
/>
</Row>
</Col>
);
};
const onSubmit = (token) => {
setCaptchaToken(token);
};
const renderCheck = () => {
if (captchaType === "Default") {
return renderDefaultCaptcha();
} else {
return (
<Col>
<Row>
<CaptchaWidget
captchaType={captchaType}
subType={subType}
siteKey={clientId}
clientSecret={secret}
onChange={onSubmit}
clientId2={clientId2}
clientSecret2={secret2}
/>
</Row>
</Col>
);
setOpen(true);
}
};
@@ -146,6 +63,16 @@ export const CaptchaPreview = ({
return false;
};
const onOk = (captchaType, captchaToken, secret) => {
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
setOpen(false);
});
};
const onCancel = () => {
setOpen(false);
};
return (
<React.Fragment>
<Button
@@ -156,20 +83,20 @@ export const CaptchaPreview = ({
>
{i18next.t("general:Preview")}
</Button>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
visible={visible}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
onOk={handleOk}
onCancel={handleCancel}
width={348}
>
{renderCheck()}
</Modal>
<CaptchaModal
owner={owner}
name={name}
captchaType={captchaType}
subType={subType}
clientId={clientId}
clientId2={clientId2}
clientSecret={clientSecret}
clientSecret2={clientSecret2}
open={open}
onOk={onOk}
onCancel={onCancel}
canCancel={true}
/>
</React.Fragment>
);
};

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token läuft ab - Tooltip",
"Token format": "Token-Format",
"Token format - Tooltip": "Token-Format - Tooltip",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Bitgröße",
@@ -695,6 +697,7 @@
"Old Password": "Altes Passwort",
"Password": "Passwort",
"Password Set": "Passwort setzen",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Eigenschaften",
"Re-enter New": "Neu erneut eingeben",
"Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Bit size",
@@ -695,6 +697,7 @@
"Old Password": "Old Password",
"Password": "Password",
"Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties",
"Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...",

View File

@@ -45,7 +45,9 @@
"Token expire - Tooltip": "Expiración del Token - Tooltip",
"Token format": "Formato del Token",
"Token format - Tooltip": "Formato del Token - Tooltip",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Tamaño del Bit",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
"Token format": "Format du jeton",
"Token format - Tooltip": "Format du jeton - infobulle",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Taille du bit",
@@ -695,6 +697,7 @@
"Old Password": "Ancien mot de passe",
"Password": "Mot de passe",
"Password Set": "Mot de passe défini",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Propriétés",
"Re-enter New": "Nouvelle entrée",
"Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
"Token format": "トークンのフォーマット",
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "ビットサイズ",
@@ -695,6 +697,7 @@
"Old Password": "古いパスワード",
"Password": "パスワード",
"Password Set": "パスワード設定",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "プロパティー",
"Re-enter New": "新しい入力",
"Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip",
"rule": "rule"
"Rule": "Rule",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Bit size",
@@ -695,6 +697,7 @@
"Old Password": "Old Password",
"Password": "Password",
"Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties",
"Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Истек токен - Подсказка",
"Token format": "Формат токена",
"Token format - Tooltip": "Формат токена - Подсказка",
"rule": "правило"
"Rule": "правило",
"None": "None",
"Always": "Always"
},
"cert": {
"Bit size": "Размер бита",
@@ -695,6 +697,7 @@
"Old Password": "Старый пароль",
"Password": "Пароль",
"Password Set": "Пароль установлен",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Свойства",
"Re-enter New": "Введите еще раз",
"Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Access Token过期时间",
"Token format": "Access Token格式",
"Token format - Tooltip": "Access Token格式",
"rule": "规则"
"Rule": "规则",
"None": "关闭",
"Always": "始终开启"
},
"cert": {
"Bit size": "位大小",
@@ -695,6 +697,7 @@
"Old Password": "旧密码",
"Password": "密码",
"Password Set": "密码已设置",
"Please select avatar from resources": "从资源中选择...",
"Properties": "属性",
"Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...",