Compare commits

...

11 Commits

Author SHA1 Message Date
e8a7b7ee9c feat: support all captcha for login (#1619)
* refactor: captcha modal

* feat: support all captcha when login

* chore: improve i18 in loginPage.js
2023-03-05 20:31:46 +08:00
f8bc87eb4e Fix i18n error 2023-03-04 00:13:29 +08:00
3e6ef9e666 Fix forget page i18n 2023-03-03 23:55:48 +08:00
ef3d323f63 Improve SMS code 2023-03-03 22:44:22 +08:00
wht
aad9201b24 feat: add SMS test feature (#1606)
* feat: add SMS test

* fix: Add missing translation

* fix: Delete redundant information

* fix: remove unnecessary field

* Update sms.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-03 22:15:02 +08:00
46f090361e Improve init_data json 2023-03-03 21:32:06 +08:00
1ae6adff8e fix(secure): remove user list from roles and permissions field to avoid leaking userlist (#1614)
* fix(secure): remove user list from roles and permissions field to avoid leaking userlist

Signed-off-by: fengxsong <fengxsong@outlook.com>

* Update permission.go

* Update role.go

---------

Signed-off-by: fengxsong <fengxsong@outlook.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-03 18:18:41 +08:00
59c95ca8a0 feat: fix ID parsing bug when calling api/logout (#1611)
Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-03-03 14:26:31 +08:00
ca1b5feb78 Improve default captcha UI 2023-03-02 22:04:37 +08:00
e50c832ff9 Fix login width 2023-03-02 20:49:13 +08:00
8696b08db2 fix: empty countryCode of current account causes crash (#1603)
* fix: empty countryCode of current account cause crush

* Update UserEditPage.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-01 22:09:48 +08:00
34 changed files with 466 additions and 402 deletions

View File

@ -160,7 +160,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if method == "POST" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information

View File

@ -86,8 +86,8 @@ func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string)
}
type captchaResponse struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
Code string `json:"Code"`
Message string `json:"Message"`
}
captchaResp := &captchaResponse{}
@ -96,8 +96,8 @@ func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string)
return false, err
}
if captchaResp.Code != 100 {
return false, errors.New(captchaResp.Msg)
if captchaResp.Code != "100" {
return false, errors.New(captchaResp.Message)
}
return true, nil

View File

@ -296,7 +296,9 @@ func (c *ApiController) Logout() {
c.ClearUserSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
object.DeleteSessionId(util.GetSessionId(object.CasdoorOrganization, object.CasdoorApplication, user), c.Ctx.Input.CruSession.SessionID())
owner, username := util.GetOwnerAndNameFromId(user)
object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))

View File

@ -20,6 +20,7 @@ package controllers
import (
"encoding/json"
"fmt"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -97,8 +98,11 @@ func (c *ApiController) SendEmail() {
return
}
code := "123456"
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(emailForm.Content, code)
for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil {
c.ResponseError(err.Error())
return
@ -130,18 +134,9 @@ func (c *ApiController) SendSms() {
return
}
var invalidReceivers []string
for idx, receiver := range smsForm.Receivers {
// The receiver phone format: E164 like +8613854673829 +441932567890
if !util.IsPhoneValid(receiver, "") {
invalidReceivers = append(invalidReceivers, receiver)
} else {
smsForm.Receivers[idx] = receiver
}
}
invalidReceivers := getInvalidSmsReceivers(smsForm)
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), invalidReceivers))
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", ")))
return
}

View File

@ -197,3 +197,14 @@ func checkQuotaForUser(count int) error {
}
return nil
}
func getInvalidSmsReceivers(smsForm SmsForm) []string {
var invalidReceivers []string
for _, receiver := range smsForm.Receivers {
// The receiver phone format: E164 like +8613854673829 +441932567890
if !util.IsPhoneValid(receiver, "") {
invalidReceivers = append(invalidReceivers, receiver)
}
}
return invalidReceivers
}

View File

@ -51,8 +51,8 @@ func (c *ApiController) SendVerificationCode() {
dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("countryCode")
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
applicationId := c.Ctx.Request.Form.Get("applicationId")
method := c.Ctx.Request.Form.Get("method")
checkUser := c.Ctx.Request.Form.Get("checkUser")
@ -76,15 +76,15 @@ func (c *ApiController) SendVerificationCode() {
}
if checkType != "none" {
if checkKey == "" {
c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
if captchaToken == "" {
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
return
}
if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil {
c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType)
return
} else if isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId); err != nil {
} else if isHuman, err := captchaProvider.VerifyCaptcha(captchaToken, clientSecret); err != nil {
c.ResponseError(err.Error())
return
} else if !isHuman {

View File

@ -6,10 +6,18 @@
"displayName": "",
"websiteUrl": "",
"favicon": "",
"passwordType": "",
"countryCodes": [""],
"passwordType": "plain",
"passwordSalt": "",
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"],
"defaultAvatar": "",
"tags": [""]
"defaultApplication": "",
"tags": [],
"languages": ["en", "zh", "es", "fr", "de", "ja", "ko", "ru", "vi"],
"masterPassword": "",
"initScore": 2000,
"enableSoftDeletion": false,
"isProfilePublic": true,
"accountItems": []
}
],
"applications": [

View File

@ -380,7 +380,7 @@ func CheckToEnableCaptcha(application *Application) bool {
if providerItem.Provider == nil {
continue
}
if providerItem.Provider.Category == "Captcha" && providerItem.Provider.Type == "Default" {
if providerItem.Provider.Category == "Captcha" {
return providerItem.Rule == "Always"
}
}

View File

@ -79,19 +79,21 @@ func initBuiltInOrganization() bool {
}
organization = &Organization{
Owner: "admin",
Name: "built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain",
CountryCodes: []string{"CN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru", "vi"},
InitScore: 2000,
AccountItems: getBuiltInAccountItems(),
Owner: "admin",
Name: "built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain",
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru", "vi"},
InitScore: 2000,
AccountItems: getBuiltInAccountItems(),
EnableSoftDeletion: false,
IsProfilePublic: false,
}
AddOrganization(organization)
return false

View File

@ -245,6 +245,10 @@ func GetPermissionsByUser(userId string) []*Permission {
panic(err)
}
for i := range permissions {
permissions[i].Users = nil
}
return permissions
}

View File

@ -159,6 +159,10 @@ func GetRolesByUser(userId string) []*Role {
panic(err)
}
for i := range roles {
roles[i].Users = nil
}
return roles
}

View File

@ -17,26 +17,39 @@ package object
import (
"strings"
"github.com/casdoor/go-sms-sender"
sender "github.com/casdoor/go-sms-sender"
)
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
if provider.Type == go_sms_sender.HuaweiCloud {
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
func getSmsClient(provider *Provider) (sender.SmsClient, error) {
var client sender.SmsClient
var err error
if provider.Type == sender.HuaweiCloud {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
}
if err != nil {
return nil, err
}
return client, nil
}
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := getSmsClient(provider)
if err != nil {
return err
}
if provider.Type == go_sms_sender.Aliyun {
if provider.Type == sender.Aliyun {
for i, number := range phoneNumbers {
phoneNumbers[i] = strings.TrimPrefix(number, "+")
}
}
params := map[string]string{}
if provider.Type == go_sms_sender.TencentCloud {
if provider.Type == sender.TencentCloud {
params["0"] = content
} else {
params["code"] = content

View File

@ -20,9 +20,11 @@ import * as Setting from "./Setting";
import i18next from "i18next";
import {authConfig} from "./auth/Auth";
import * as ProviderEditTestEmail from "./TestEmailWidget";
import * as ProviderEditTestSms from "./TestSmsWidget";
import copy from "copy-to-clipboard";
import {CaptchaPreview} from "./common/CaptchaPreview";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import {PhoneNumberInput} from "./common/PhoneNumberInput";
const {Option} = Select;
const {TextArea} = Input;
@ -596,7 +598,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email Title"), i18next.t("provider:Email Title - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.title} onChange={e => {
@ -606,7 +608,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
</Col>
<Col span={22} >
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={this.state.provider.content} onChange={e => {
@ -629,7 +631,7 @@ class ProviderEditPage extends React.Component {
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
{i18next.t("provider:Send Test Email")}
{i18next.t("provider:Send Testing Email")}
</Button>
</Row>
</React.Fragment>
@ -659,6 +661,36 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
</Col>
<Col span={4} >
<Input.Group compact>
<PhoneNumberInput
style={{width: "30%"}}
value={this.state.provider.content}
onChange={(value) => {
this.updateProviderField("content", value);
}}
countryCodes={this.props.account.organization.countryCodes}
/>
<Input value={this.state.provider.receiver}
style={{width: "70%"}}
placeholder = {i18next.t("user:Input your phone number")}
onChange={e => {
this.updateProviderField("receiver", e.target.value);
}} />
</Input.Group>
</Col>
<Col span={2} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidPhone(this.state.provider.receiver)}
onClick={() => ProviderEditTestSms.sendTestSms(this.state.provider, "+" + Setting.getCountryCode(this.state.provider.content) + this.state.provider.receiver)} >
{i18next.t("provider:Send Testing SMS")}
</Button>
</Col>
</Row>
</React.Fragment>
) : this.state.provider.category === "SAML" ? (
<React.Fragment>
@ -781,17 +813,17 @@ class ProviderEditPage extends React.Component {
</Col>
<Col span={22} >
<CaptchaPreview
owner={this.state.provider.owner}
name={this.state.provider.name}
provider={this.state.provider}
providerName={this.state.providerName}
clientSecret={this.state.provider.clientSecret}
captchaType={this.state.provider.type}
subType={this.state.provider.subType}
owner={this.state.provider.owner}
clientId={this.state.provider.clientId}
name={this.state.provider.name}
providerUrl={this.state.provider.providerUrl}
clientSecret={this.state.provider.clientSecret}
clientId2={this.state.provider.clientId2}
clientSecret2={this.state.provider.clientSecret2}
providerUrl={this.state.provider.providerUrl}
/>
</Col>
</Row>

View File

@ -25,7 +25,7 @@ export const ResetModal = (props) => {
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [dest, setDest] = React.useState("");
const [code, setCode] = React.useState("");
const {buttonText, destType, application, account} = props;
const {buttonText, destType, application, countryCode} = props;
const showModal = () => {
setVisible(true);
@ -87,7 +87,7 @@ export const ResetModal = (props) => {
<Row style={{width: "100%", marginBottom: "20px"}}>
<Input
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
prefix={destType === "email" ? <React.Fragment><MailOutlined />&nbsp;&nbsp;</React.Fragment> : (<React.Fragment><PhoneOutlined />&nbsp;&nbsp;{`+${Setting.getCountryCode(account.countryCode)}`}&nbsp;</React.Fragment>)}
prefix={destType === "email" ? <React.Fragment><MailOutlined />&nbsp;&nbsp;</React.Fragment> : (<React.Fragment><PhoneOutlined />&nbsp;&nbsp;{countryCode !== "" ? "+" : null}{Setting.getCountryCode(countryCode)}&nbsp;</React.Fragment>)}
placeholder={placeholder}
onChange={e => setDest(e.target.value)}
/>

View File

@ -209,7 +209,10 @@ export function initCountries() {
}
export function getCountryCode(country) {
return phoneNumber.getCountryCallingCode(country);
if (phoneNumber.isSupportedCountry(country)) {
return phoneNumber.getCountryCallingCode(country);
}
return "";
}
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {

View File

@ -19,7 +19,7 @@ export function sendTestEmail(provider, email) {
testEmailProvider(provider, email)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully send email");
Setting.showMessage("success", `${i18next.t("provider:Email sent successfully")}`);
} else {
Setting.showMessage("error", res.msg);
}
@ -33,7 +33,7 @@ export function connectSmtpServer(provider) {
testEmailProvider(provider)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully connecting smtp server");
Setting.showMessage("success", "provider:SMTP connected successfully");
} else {
Setting.showMessage("error", res.msg);
}

43
web/src/TestSmsWidget.js Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2023 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 * as Setting from "./Setting";
import i18next from "i18next";
export function sendTestSms(provider, phone) {
testSmsProvider(provider, phone)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", `${i18next.t("provider:SMS sent successfully")}`);
} else {
Setting.showMessage("error", res.msg);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
function testSmsProvider(provider, phone = "") {
const SmsForm = {
content: "123456",
receivers: [phone],
};
return fetch(`${Setting.ServerUrl}/api/send-sms?provider=` + provider.name, {
method: "POST",
credentials: "include",
body: JSON.stringify(SmsForm),
}).then(res => res.json());
}

View File

@ -138,6 +138,10 @@ class UserEditPage extends React.Component {
return this.isSelf() || Setting.isAdminUser(this.props.account);
}
getCountryCode() {
return this.props.account.countryCode;
}
renderAccountItem(accountItem) {
if (!accountItem.visible) {
return null;
@ -296,7 +300,7 @@ class UserEditPage extends React.Component {
</Col>
<Col span={Setting.isMobile() ? 22 : 11} >
{/* backend auto get the current user, so admin can not edit. Just self can reset*/}
{this.isSelf() ? <ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
{this.isSelf() ? <ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
</Col>
</Row>
);
@ -326,7 +330,7 @@ class UserEditPage extends React.Component {
</Input.Group>
</Col>
<Col span={Setting.isMobile() ? 24 : 11} >
{this.isSelf() ? (<ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
{this.isSelf() ? (<ResetModal application={this.state.application} countryCode={this.getCountryCode()} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col>
</Row>
);

View File

@ -212,9 +212,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
message: i18next.t("application:Please input your application!"),
},
]}
/>
@ -224,9 +222,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
message: i18next.t("application:Please input your organization!"),
},
]}
/>
@ -235,9 +231,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your username!"
),
message: i18next.t("forget:Please input your username!"),
whitespace: true,
},
]}
@ -287,9 +281,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
message: i18next.t("application:Please input your application!"),
},
]}
/>
@ -299,9 +291,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
message: i18next.t("application:Please input your organization!"),
},
]}
/>
@ -327,9 +317,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"code:Please input your verification code!"
),
message: i18next.t("code:Please input your verification code!"),
},
]}
>
@ -377,9 +365,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
message: i18next.t("application:Please input your application!"),
},
]}
/>
@ -389,9 +375,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
message: i18next.t("application:Please input your organization!"),
},
]}
/>
@ -401,9 +385,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your password!"
),
message: i18next.t("login:Please input your password!"),
},
]}
hasFeedback
@ -421,9 +403,7 @@ class ForgetPage extends React.Component {
rules={[
{
required: true,
message: i18next.t(
"forget:Please confirm your password!"
),
message: i18next.t("signup:Please confirm your password!"),
},
({getFieldValue}) => ({
validator(rule, value) {
@ -431,9 +411,7 @@ class ForgetPage extends React.Component {
return Promise.resolve();
}
return Promise.reject(
i18next.t(
"forget:Your confirmed password is inconsistent with the password!"
)
i18next.t("signup:Your confirmed password is inconsistent with the password!")
);
},
}),
@ -497,11 +475,11 @@ class ForgetPage extends React.Component {
icon: <UserOutlined />,
},
{
title: i18next.t("forget:Account"),
title: i18next.t("forget:Verify"),
icon: <SolutionOutlined />,
},
{
title: i18next.t("forget:Account"),
title: i18next.t("forget:Reset"),
icon: <KeyOutlined />,
},
]}

View File

@ -82,13 +82,13 @@ class LoginPage extends React.Component {
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.application && !prevState.application) {
const defaultCaptchaProviderItems = this.getDefaultCaptchaProviderItems(this.state.application);
const captchaProviderItems = this.getCaptchaProviderItems(this.state.application);
if (!defaultCaptchaProviderItems) {
if (!captchaProviderItems) {
return;
}
this.setState({enableCaptchaModal: defaultCaptchaProviderItems.some(providerItem => providerItem.rule === "Always")});
this.setState({enableCaptchaModal: captchaProviderItems.some(providerItem => providerItem.rule === "Always")});
}
}
@ -228,7 +228,7 @@ class LoginPage extends React.Component {
Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
}
} else {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
Setting.showMessage("error", `${i18next.t("application:Failed to log in")}: ${res.msg}`);
}
});
} else {
@ -262,13 +262,7 @@ class LoginPage extends React.Component {
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);
},
values: values,
});
} else {
this.login(values);
@ -290,8 +284,6 @@ class LoginPage extends React.Component {
}
Setting.showMessage("success", msg);
this.setState({openCaptchaModal: false});
if (casParams.service !== "") {
const st = res.data;
const newUrl = new URL(casParams.service);
@ -299,8 +291,7 @@ class LoginPage extends React.Component {
window.location.href = newUrl.toString();
}
} else {
this.setState({openCaptchaModal: false});
Setting.showMessage("error", `Failed to log in: ${res.msg}`);
Setting.showMessage("error", `${i18next.t("application:Failed to log in")}: ${res.msg}`);
}
});
} else {
@ -338,8 +329,7 @@ class LoginPage extends React.Component {
}
}
} else {
this.setState({openCaptchaModal: false});
Setting.showMessage("error", `Failed to log in: ${res.msg}`);
Setting.showMessage("error", `${i18next.t("application:Failed to log in")}: ${res.msg}`);
}
});
}
@ -378,6 +368,13 @@ class LoginPage extends React.Component {
}
if (application.enablePassword) {
let loginWidth = 320;
if (Setting.getLanguage() === "fr") {
loginWidth += 10;
} else if (Setting.getLanguage() === "es") {
loginWidth += 40;
}
return (
<Form
name="normal_login"
@ -391,7 +388,7 @@ class LoginPage extends React.Component {
onFinish={(values) => {
this.onFinish(values);
}}
style={{width: "300px"}}
style={{width: `${loginWidth}px`}}
size="large"
ref={this.form}
>
@ -542,7 +539,7 @@ class LoginPage extends React.Component {
}
}
getDefaultCaptchaProviderItems(application) {
getCaptchaProviderItems(application) {
const providers = application?.providers;
if (providers === undefined || providers === null) {
@ -554,7 +551,7 @@ class LoginPage extends React.Component {
return false;
}
return providerItem.provider.category === "Captcha" && providerItem.provider.type === "Default";
return providerItem.provider.category === "Captcha";
});
}
@ -563,22 +560,25 @@ class LoginPage extends React.Component {
return null;
}
const provider = this.getDefaultCaptchaProviderItems(application)
const provider = this.getCaptchaProviderItems(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}
visible={this.state.openCaptchaModal}
onOk={(captchaType, captchaToken, clientSecret) => {
const values = this.state.values;
values["captchaType"] = captchaType;
values["captchaToken"] = captchaToken;
values["clientSecret"] = clientSecret;
this.login(values);
this.setState({openCaptchaModal: false});
}}
onCancel={() => this.setState({openCaptchaModal: false})}
isCurrentProvider={true}
/>;
}

View File

@ -109,11 +109,11 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
}).then(res => res.json());
}
export function sendCode(checkType, checkId, checkKey, method, countryCode = "", dest, type, applicationId, checkUser = "") {
export function sendCode(checkType, captchaToken, clientSecret, method, countryCode = "", dest, type, applicationId, checkUser = "") {
const formData = new FormData();
formData.append("checkType", checkType);
formData.append("checkId", checkId);
formData.append("checkKey", checkKey);
formData.append("captchaToken", captchaToken);
formData.append("clientSecret", clientSecret);
formData.append("method", method);
formData.append("countryCode", countryCode);
formData.append("dest", dest);

View File

@ -19,94 +19,97 @@ 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);
export const CaptchaModal = (props) => {
const {owner, name, visible, onOk, onCancel, isCurrentProvider} = props;
const [captchaType, setCaptchaType] = React.useState("none");
const [clientId, setClientId] = React.useState("");
const [clientSecret, setClientSecret] = React.useState("");
const [subType, setSubType] = React.useState("");
const [clientId2, setClientId2] = React.useState("");
const [clientSecret2, setClientSecret2] = React.useState("");
const [open, setOpen] = 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 defaultInputRef = React.useRef(null);
useEffect(() => {
setVisible(() => {
if (open) {
getCaptchaFromBackend();
} else {
cleanUp();
}
return open;
});
}, [open]);
if (visible) {
loadCaptcha();
} else {
handleCancel();
setOpen(false);
}
}, [visible]);
const handleOk = () => {
onOk?.(captchaType, captchaToken, secret);
onOk?.(captchaType, captchaToken, clientSecret);
};
const handleCancel = () => {
setCaptchaToken("");
onCancel?.();
};
const cleanUp = () => {
setCaptchaToken("");
};
const getCaptchaFromBackend = () => {
UserBackend.getCaptcha(owner, name, true).then((res) => {
if (captchaType === "Default") {
setSecret(res.captchaId);
const loadCaptcha = () => {
UserBackend.getCaptcha(owner, name, isCurrentProvider).then((res) => {
if (res.type === "none") {
handleOk();
} else if (res.type === "Default") {
setOpen(true);
setClientSecret(res.captchaId);
setCaptchaImg(res.captchaImage);
setCaptchaType("Default");
} else {
setSecret(res.clientSecret);
setSecret2(res.clientSecret2);
setOpen(true);
setCaptchaType(res.type);
setClientId(res.clientId);
setClientSecret(res.clientSecret);
setSubType(res.subType);
setClientId2(res.clientId2);
setClientSecret2(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)}
<Col style={{textAlign: "center"}}>
<div style={{display: "inline-block"}}>
<Row
style={{
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
backgroundRepeat: "no-repeat",
height: "80px",
width: "200px",
borderRadius: "5px",
border: "1px solid #ccc",
marginBottom: "20px",
}}
/>
</Row>
<Row>
<Input
ref={defaultInputRef}
style={{width: "200px"}}
value={captchaToken}
prefix={<SafetyOutlined />}
placeholder={i18next.t("general:Captcha")}
onPressEnter={handleOk}
onChange={(e) => setCaptchaToken(e.target.value)}
/>
</Row>
</div>
</Col>
);
};
const onSubmit = (token) => {
const onChange = (token) => {
setCaptchaToken(token);
};
const renderCheck = () => {
const renderCaptcha = () => {
if (captchaType === "Default") {
return renderDefaultCaptcha();
} else {
@ -117,10 +120,10 @@ export const CaptchaModal = ({
captchaType={captchaType}
subType={subType}
siteKey={clientId}
clientSecret={secret}
onChange={onSubmit}
clientSecret={clientSecret}
onChange={onChange}
clientId2={clientId2}
clientSecret2={secret2}
clientSecret2={clientSecret2}
/>
</Row>
</Col>
@ -129,31 +132,41 @@ export const CaptchaModal = ({
};
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>,
];
let isOkDisabled = false;
if (captchaType === "Default") {
const regex = /^\d{5}$/;
if (!regex.test(captchaToken)) {
isOkDisabled = true;
}
} else if (captchaToken === "") {
isOkDisabled = true;
}
return [
<Button key="cancel" onClick={handleCancel}>{i18next.t("user:Cancel")}</Button>,
<Button key="ok" disabled={isOkDisabled} onClick={handleOk}>{i18next.t("user:OK")}</Button>,
];
};
return (
<React.Fragment>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
open={visible}
width={348}
footer={renderFooter()}
>
{renderCheck()}
</Modal>
</React.Fragment>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
open={open}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
width={350}
footer={renderFooter()}
onCancel={handleCancel}
onOk={handleOk}
>
<div style={{marginTop: "20px", marginBottom: "50px"}}>
{
renderCaptcha()
}
</div>
</Modal>
);
};

View File

@ -18,19 +18,9 @@ import i18next from "i18next";
import {CaptchaModal} from "./CaptchaModal";
import * as UserBackend from "../backend/UserBackend";
export const CaptchaPreview = ({
provider,
clientSecret,
captchaType,
subType,
owner,
clientId,
name,
providerUrl,
clientId2,
clientSecret2,
}) => {
const [open, setOpen] = React.useState(false);
export const CaptchaPreview = (props) => {
const {owner, name, provider, captchaType, subType, clientId, clientSecret, clientId2, clientSecret2, providerUrl} = props;
const [visible, setVisible] = React.useState(false);
const clickPreview = () => {
provider.name = name;
@ -42,13 +32,13 @@ export const CaptchaPreview = ({
// ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
// setOpen(true);
// });
setOpen(true);
setVisible(true);
} else {
setOpen(true);
setVisible(true);
}
};
const getButtonDisabled = () => {
const isButtonDisabled = () => {
if (captchaType !== "Default") {
if (!clientId || !clientSecret) {
return true;
@ -62,14 +52,14 @@ export const CaptchaPreview = ({
return false;
};
const onOk = (captchaType, captchaToken, secret) => {
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
setOpen(false);
const onOk = (captchaType, captchaToken, clientSecret) => {
UserBackend.verifyCaptcha(captchaType, captchaToken, clientSecret).then(() => {
setVisible(false);
});
};
const onCancel = () => {
setOpen(false);
setVisible(false);
};
return (
@ -78,23 +68,17 @@ export const CaptchaPreview = ({
style={{fontSize: 14}}
type={"primary"}
onClick={clickPreview}
disabled={getButtonDisabled()}
disabled={isButtonDisabled()}
>
{i18next.t("general:Preview")}
</Button>
<CaptchaModal
owner={owner}
name={name}
captchaType={captchaType}
subType={subType}
clientId={clientId}
clientId2={clientId2}
clientSecret={clientSecret}
clientSecret2={clientSecret2}
open={open}
visible={visible}
onOk={onOk}
onCancel={onCancel}
canCancel={true}
isCurrentProvider={true}
/>
</React.Fragment>
);

View File

@ -14,7 +14,9 @@
import React, {useEffect} from "react";
export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onChange, clientId2, clientSecret2}) => {
export const CaptchaWidget = (props) => {
const {captchaType, subType, siteKey, clientSecret, clientId2, clientSecret2, onChange} = props;
const loadScript = (src) => {
const tag = document.createElement("script");
tag.async = false;

View File

@ -12,29 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Input, Modal, Row} from "antd";
import {Button, Input} from "antd";
import React from "react";
import i18next from "i18next";
import * as UserBackend from "../backend/UserBackend";
import {SafetyOutlined} from "@ant-design/icons";
import {CaptchaWidget} from "./CaptchaWidget";
import {CaptchaModal} from "./CaptchaModal";
const {Search} = Input;
export const SendCodeInput = (props) => {
const {disabled, textBefore, onChange, onButtonClickArgs, application, method, countryCode} = props;
const [visible, setVisible] = React.useState(false);
const [key, setKey] = React.useState("");
const [captchaImg, setCaptchaImg] = React.useState("");
const [checkType, setCheckType] = React.useState("");
const [checkId, setCheckId] = React.useState("");
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
const [buttonLoading, setButtonLoading] = React.useState(false);
const [buttonDisabled, setButtonDisabled] = React.useState(true);
const [clientId, setClientId] = React.useState("");
const [subType, setSubType] = React.useState("");
const [clientId2, setClientId2] = React.useState("");
const [clientSecret2, setClientSecret2] = React.useState("");
const handleCountDown = (leftTime = 60) => {
let leftTimeSecond = leftTime;
@ -50,11 +42,10 @@ export const SendCodeInput = (props) => {
setTimeout(countDown, 1000);
};
const handleOk = () => {
const handleOk = (captchaType, captchaToken, clintSecret) => {
setVisible(false);
setButtonLoading(true);
UserBackend.sendCode(checkType, checkId, key, method, countryCode, ...onButtonClickArgs).then(res => {
setKey("");
UserBackend.sendCode(captchaType, captchaToken, clintSecret, method, countryCode, ...onButtonClickArgs).then(res => {
setButtonLoading(false);
if (res) {
handleCountDown(60);
@ -64,76 +55,6 @@ export const SendCodeInput = (props) => {
const handleCancel = () => {
setVisible(false);
setKey("");
};
const loadCaptcha = () => {
UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
if (res.type === "none") {
UserBackend.sendCode("none", "", "", method, countryCode, ...onButtonClickArgs).then(res => {
if (res) {
handleCountDown(60);
}
});
} else if (res.type === "Default") {
setCheckId(res.captchaId);
setCaptchaImg(res.captchaImage);
setCheckType("Default");
setVisible(true);
} else {
setCheckType(res.type);
setClientId(res.clientId);
setCheckId(res.clientSecret);
setVisible(true);
setSubType(res.subType);
setClientId2(res.clientId2);
setClientSecret2(res.clientSecret2);
}
});
};
const renderCaptcha = () => {
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={key} prefix={<SafetyOutlined />} placeholder={i18next.t("general:Captcha")} onPressEnter={handleOk} onChange={e => setKey(e.target.value)} />
</Row>
</Col>
);
};
const onSubmit = (token) => {
setButtonDisabled(false);
setKey(token);
};
const renderCheck = () => {
if (checkType === "Default") {
return renderCaptcha();
} else {
return (
<CaptchaWidget
captchaType={checkType}
subType={subType}
siteKey={clientId}
clientSecret={checkId}
onChange={onSubmit}
clientId2={clientId2}
clientSecret2={clientSecret2}
/>
);
}
};
return (
@ -149,25 +70,16 @@ export const SendCodeInput = (props) => {
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
</Button>
}
onSearch={loadCaptcha}
onSearch={() => setVisible(true)}
/>
<Modal
closable={false}
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
open={visible}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
<CaptchaModal
owner={application.owner}
name={application.name}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{disabled: key.length !== 5 && buttonDisabled}}
width={348}
>
{
renderCheck()
}
</Modal>
isCurrentProvider={false}
/>
</React.Fragment>
);
};

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip",
"Enable signup": "Anmeldung aktivieren",
"Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Anmeldung fehlgeschlagen",
"File uploaded successfully": "Datei erfolgreich hochgeladen",
"Follow organization theme": "Follow organization theme",
@ -311,7 +312,7 @@
"No account?": "Kein Konto?",
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
"Password": "Passwort",
"Password - Tooltip": "Passwort - Tooltip",
"Password - Tooltip": "Password - Tooltip",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "Bitte gib deinen Code ein!",
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
@ -514,10 +515,11 @@
"Domain": "Domäne",
"Domain - Tooltip": "Storage endpoint custom domain",
"Edit Provider": "Anbieter bearbeiten",
"Email Content": "Email content",
"Email Content - Tooltip": "Unique string-style identifier",
"Email Title": "E-Mail-Titel",
"Email Title - Tooltip": "Unique string-style identifier",
"Email content": "Email content",
"Email content - Tooltip": "Unique string-style identifier",
"Email sent successfully": "Email sent successfully",
"Email title": "E-Mail-Titel",
"Email title - Tooltip": "Unique string-style identifier",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Region Endpunkt für Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "SMS sent successfully",
"SP ACS URL": "SP-ACS-URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Schild Name",
"Sign Name - Tooltip": "Unique string-style identifier",
"Sign request": "Signaturanfrage",
@ -649,7 +655,7 @@
"The input is not valid Email!": "Die Eingabe ist ungültig!",
"The input is not valid Phone!": "Die Eingabe ist nicht gültig!",
"Username": "Benutzername",
"Username - Tooltip": "Benutzername - Tooltip",
"Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "Ihr Konto wurde erstellt!",
"Your confirmed password is inconsistent with the password!": "Ihr bestätigtes Passwort stimmt nicht mit dem Passwort überein!",
"sign in now": "jetzt anmelden"

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Enable signup",
"Enable signup - Tooltip": "Enable signup - Tooltip",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully",
"Follow organization theme": "Follow organization theme",
@ -514,10 +515,11 @@
"Domain": "Domain",
"Domain - Tooltip": "Domain - Tooltip",
"Edit Provider": "Edit Provider",
"Email Content": "Email Content",
"Email Content - Tooltip": "Email Content - Tooltip",
"Email Title": "Email Title",
"Email Title - Tooltip": "Email Title - Tooltip",
"Email content": "Email content",
"Email content - Tooltip": "Email content - Tooltip",
"Email sent successfully": "Email sent successfully",
"Email title": "Email title",
"Email title - Tooltip": "Email title - Tooltip",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "SMS sent successfully",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Sign Name",
"Sign Name - Tooltip": "Sign Name - Tooltip",
"Sign request": "Sign request",

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Habilitar nuevos registros",
"Enable signup - Tooltip": "Habilitar nuevos registros - Tooltip",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "El archivo ha sido subido con éxito",
"Follow organization theme": "Follow organization theme",
@ -311,7 +312,7 @@
"No account?": "¿No estás registrado?",
"Or sign in with another account": "O inicia sesión con otra cuenta",
"Password": "Contraseña",
"Password - Tooltip": "Contraseña - Tooltip",
"Password - Tooltip": "Password - Tooltip",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "¡Por favor ingrese su código!",
"Please input your password!": "¡Por favor ingrese su contraseña!",
@ -514,10 +515,11 @@
"Domain": "Dominio",
"Domain - Tooltip": "Dominio - Tooltip",
"Edit Provider": "Editar Proveedor",
"Email Content": "Contenido del Email",
"Email Content - Tooltip": "Contenido del Email - Tooltip",
"Email Title": "Titulo del Email",
"Email Title - Tooltip": "Titulo del Email - Tooltip",
"Email content": "Contenido del Email",
"Email content - Tooltip": "Contenido del Email - Tooltip",
"Email sent successfully": "Email sent successfully",
"Email title": "Titulo del Email",
"Email title - Tooltip": "Titulo del Email - Tooltip",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "SMS sent successfully",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
"Send Test Email": "Enviar email de prueba",
"Send Testing Email": "Enviar email de prueba",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Sign Name",
"Sign Name - Tooltip": "Sign Name - Tooltip",
"Sign request": "Sign request",
@ -649,7 +655,7 @@
"The input is not valid Email!": "El valor ingresado no es un Email válido!",
"The input is not valid Phone!": "El valor ingresado no es un Teléfono válido!",
"Username": "Nombre de usuario",
"Username - Tooltip": "Nombre de usuario - Tooltip",
"Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "¡Tu cuenta ha sido creada!",
"Your confirmed password is inconsistent with the password!": "¡La confirmación de su contraseña es inconsistente!",
"sign in now": "iniciar sesión ahora"

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Activer la session de connexion - infobulle",
"Enable signup": "Activer l'inscription",
"Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "Fichier téléchargé avec succès",
"Follow organization theme": "Follow organization theme",
@ -311,7 +312,7 @@
"No account?": "Pas de compte ?",
"Or sign in with another account": "Ou connectez-vous avec un autre compte",
"Password": "Mot de passe",
"Password - Tooltip": "Mot de passe - Info-bulle",
"Password - Tooltip": "Password - Tooltip",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "Veuillez saisir votre code !",
"Please input your password!": "Veuillez saisir votre mot de passe !",
@ -514,10 +515,11 @@
"Domain": "Domaine",
"Domain - Tooltip": "Storage endpoint custom domain",
"Edit Provider": "Modifier le fournisseur",
"Email Content": "Email content",
"Email Content - Tooltip": "Unique string-style identifier",
"Email Title": "Titre de l'e-mail",
"Email Title - Tooltip": "Unique string-style identifier",
"Email content": "Email content",
"Email content - Tooltip": "Unique string-style identifier",
"Email sent successfully": "Email sent successfully",
"Email title": "Titre de l'e-mail",
"Email title - Tooltip": "Unique string-style identifier",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Point de terminaison de la région pour Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "SMS sent successfully",
"SP ACS URL": "URL du SP ACS",
"SP ACS URL - Tooltip": "URL SP ACS - infobulle",
"SP Entity ID": "ID de l'entité SP",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Infobulle",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Nom du panneau",
"Sign Name - Tooltip": "Unique string-style identifier",
"Sign request": "Demande de signature",
@ -649,7 +655,7 @@
"The input is not valid Email!": "L'entrée n'est pas un email valide !",
"The input is not valid Phone!": "L'entrée n'est pas un téléphone valide !",
"Username": "Nom d'utilisateur",
"Username - Tooltip": "Nom d'utilisateur - Info-bulle",
"Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "Votre compte a été créé !",
"Your confirmed password is inconsistent with the password!": "Votre mot de passe confirmé est incompatible avec le mot de passe !",
"sign in now": "connectez-vous maintenant"

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "サインアップを有効にする",
"Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "ファイルが正常にアップロードされました",
"Follow organization theme": "Follow organization theme",
@ -311,7 +312,7 @@
"No account?": "アカウントがありませんか?",
"Or sign in with another account": "または別のアカウントでサインイン",
"Password": "パスワード",
"Password - Tooltip": "パスワード → ツールチップ",
"Password - Tooltip": "Password - Tooltip",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "コードを入力してください!",
"Please input your password!": "パスワードを入力してください!",
@ -514,10 +515,11 @@
"Domain": "ドメイン",
"Domain - Tooltip": "Storage endpoint custom domain",
"Edit Provider": "プロバイダーを編集",
"Email Content": "Email content",
"Email Content - Tooltip": "Unique string-style identifier",
"Email Title": "メールタイトル",
"Email Title - Tooltip": "Unique string-style identifier",
"Email content": "Email content",
"Email content - Tooltip": "Unique string-style identifier",
"Email sent successfully": "Email sent successfully",
"Email title": "メールタイトル",
"Email title - Tooltip": "Unique string-style identifier",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "イントラネットのリージョンエンドポイント",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "短信发送成功",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - ツールチップ",
"SP Entity ID": "SP ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "シークレットアクセスキー - ツールチップ",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "署名名",
"Sign Name - Tooltip": "Unique string-style identifier",
"Sign request": "サインリクエスト",
@ -649,7 +655,7 @@
"The input is not valid Email!": "入力されたメールアドレスが無効です!",
"The input is not valid Phone!": "入力された電話番号が正しくありません!",
"Username": "ユーザー名",
"Username - Tooltip": "ユーザー名 - ツールチップ",
"Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "あなたのアカウントが作成されました!",
"Your confirmed password is inconsistent with the password!": "確認されたパスワードがパスワードと一致していません!",
"sign in now": "今すぐサインイン"

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Enable signup",
"Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully",
"Follow organization theme": "Follow organization theme",
@ -514,10 +515,11 @@
"Domain": "Domain",
"Domain - Tooltip": "Storage endpoint custom domain",
"Edit Provider": "Edit Provider",
"Email Content": "Email content",
"Email Content - Tooltip": "Unique string-style identifier",
"Email Title": "Email title",
"Email Title - Tooltip": "Unique string-style identifier",
"Email content": "Email content",
"Email content - Tooltip": "Unique string-style identifier",
"Email sent successfully": "Email sent successfully",
"Email title": "Email title",
"Email title - Tooltip": "Unique string-style identifier",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "短信发送成功",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Sign Name",
"Sign Name - Tooltip": "Unique string-style identifier",
"Sign request": "Sign request",

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Включить сеанс входа - Подсказка",
"Enable signup": "Включить регистрацию",
"Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "Файл успешно загружен",
"Follow organization theme": "Follow organization theme",
@ -311,7 +312,7 @@
"No account?": "Нет учетной записи?",
"Or sign in with another account": "Или войти с помощью другой учетной записи",
"Password": "Пароль",
"Password - Tooltip": "Пароль - Подсказка",
"Password - Tooltip": "Password - Tooltip",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your code!": "Пожалуйста, введите ваш код!",
"Please input your password!": "Пожалуйста, введите ваш пароль!",
@ -514,10 +515,11 @@
"Domain": "Домен",
"Domain - Tooltip": "Storage endpoint custom domain",
"Edit Provider": "Изменить провайдера",
"Email Content": "Email content",
"Email Content - Tooltip": "Unique string-style identifier",
"Email Title": "Заголовок письма",
"Email Title - Tooltip": "Unique string-style identifier",
"Email content": "Email content",
"Email content - Tooltip": "Unique string-style identifier",
"Email sent successfully": "Email sent successfully",
"Email title": "Заголовок письма",
"Email title - Tooltip": "Unique string-style identifier",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Конечная точка региона Интранета",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "短信发送成功",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Подсказка",
"SP Entity ID": "Идентификатор сущности SP",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Подсказка",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Имя подписи",
"Sign Name - Tooltip": "Unique string-style identifier",
"Sign request": "Запрос на подпись",
@ -649,7 +655,7 @@
"The input is not valid Email!": "Ввод не является допустимым Email!",
"The input is not valid Phone!": "Введен неверный телефон!",
"Username": "Имя пользователя",
"Username - Tooltip": "Имя пользователя - Подсказка",
"Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "Ваша учетная запись была создана!",
"Your confirmed password is inconsistent with the password!": "Подтвержденный пароль не соответствует паролю!",
"sign in now": "войти сейчас"

View File

@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Enable signup",
"Enable signup - Tooltip": "Enable signup - Tooltip",
"Failed to log in": "Failed to log in",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully",
"Follow organization theme": "Follow organization theme",
@ -514,10 +515,11 @@
"Domain": "Domain",
"Domain - Tooltip": "Domain - Tooltip",
"Edit Provider": "Edit Provider",
"Email Content": "Email Content",
"Email Content - Tooltip": "Email Content - Tooltip",
"Email Title": "Email Title",
"Email Title - Tooltip": "Email Title - Tooltip",
"Email content": "Email content",
"Email content - Tooltip": "Email content - Tooltip",
"Email sent successfully": "Email sent successfully",
"Email title": "Email title",
"Email title - Tooltip": "Email title - Tooltip",
"Enable QR code": "Enable QR code",
"Enable QR code - Tooltip": "Enable QR code - Tooltip",
"Endpoint": "Endpoint",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "SMS Test - Tooltip",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "短信发送成功",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "Secret key - Tooltip",
"SecretAccessKey - Tooltip": "SecretAccessKey - Tooltip",
"Send Test Email": "Send Test Email",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sign Name": "Sign Name",
"Sign Name - Tooltip": "Sign Name - Tooltip",
"Sign request": "Sign request",

View File

@ -27,7 +27,7 @@
"Copy signup page URL": "复制注册页面URL",
"Edit Application": "编辑应用",
"Enable SAML compress": "压缩SAML响应",
"Enable SAML compress - Tooltip": "Casdoor作为SAML idp是否压缩SAML响应信息",
"Enable SAML compress - Tooltip": "Casdoor作为SAML IdP是否压缩SAML响应信息",
"Enable WebAuthn signin": "启用WebAuthn登录",
"Enable WebAuthn signin - Tooltip": "是否支持用户在登录页面通过WebAuthn方式登录",
"Enable code signin": "启用验证码登录",
@ -38,6 +38,7 @@
"Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话",
"Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册",
"Failed to log in": "登录失败",
"Failed to sign in": "登录失败",
"File uploaded successfully": "文件上传成功",
"Follow organization theme": "使用组织主题",
@ -125,7 +126,7 @@
},
"forget": {
"Account": "账号",
"Change Password": "编辑密码",
"Change Password": "修改密码",
"Choose email or phone": "请选择邮箱或手机号验证",
"Confirm": "验证密码",
"Next Step": "下一步",
@ -514,10 +515,11 @@
"Domain": "域名",
"Domain - Tooltip": "存储节点自定义域名",
"Edit Provider": "编辑提供商",
"Email Content": "邮件内容",
"Email Content - Tooltip": "邮件内容",
"Email Title": "邮件标题",
"Email Title - Tooltip": "邮件标题",
"Email content": "邮件内容",
"Email content - Tooltip": "邮件内容",
"Email sent successfully": "邮件发送成功",
"Email title": "邮件标题",
"Email title - Tooltip": "邮件标题",
"Enable QR code": "扫码登陆",
"Enable QR code - Tooltip": "扫码登陆 - 工具提示",
"Endpoint": "地域节点 (外网)",
@ -550,8 +552,11 @@
"Region endpoint for Intranet": "地域节点 (内网)",
"Required": "是否必填项",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "测试短信配置",
"SMS Test - Tooltip": "请输入测试手机号",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip",
"SMS sent successfully": "短信发送成功",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - 工具提示",
"SP Entity ID": "SP 实体 ID",
@ -563,7 +568,8 @@
"Secret key": "Secret key",
"Secret key - Tooltip": "用于服务端调用验证码提供商API进行验证",
"SecretAccessKey - Tooltip": "访问密钥-工具提示",
"Send Test Email": "发送测试邮件",
"Send Testing Email": "发送测试邮件",
"Send Testing SMS": "发送测试短信",
"Sign Name": "签名名称",
"Sign Name - Tooltip": "签名名称",
"Sign request": "签名请求",
@ -649,7 +655,7 @@
"The input is not valid Email!": "您输入的电子邮箱格式有误!",
"The input is not valid Phone!": "您输入的手机号格式有误!",
"Username": "用户名",
"Username - Tooltip": "允许字符包括字母、数字、下划线,不得以数字开头",
"Username - Tooltip": "唯一的用户名",
"Your account has been created!": "您的账号已创建!",
"Your confirmed password is inconsistent with the password!": "您两次输入的密码不一致!",
"sign in now": "立即登录"