Compare commits

..

8 Commits

Author SHA1 Message Date
DacongDA
868e66e866 feat: fix QQ login error when using mobile browser (#2971) 2024-05-27 01:07:15 +08:00
Husile
40ad3c9234 feat: support MFA fields in syncer (#2966)
* feat:add fields of sync-database

* feat:add fields of sync-database
2024-05-27 01:06:59 +08:00
Alex Babel
e2cd0604c2 feat: add back arm64 support in Docker image (#2969) 2024-05-26 01:22:49 +08:00
Yang Luo
78c3065fbb feat: fix address field bug in user edit page 2024-05-24 17:19:27 +08:00
DacongDA
af2a9f0374 feat: get phone number and country from Google OAuth provider (#2965)
* feat: get phone number and country from Google OAuth provider

* feat: fix i18n
2024-05-23 00:42:36 +08:00
DacongDA
bfcfb56336 feat: add address line 1 and 2 in web UI (#2961) 2024-05-19 23:55:38 +08:00
DacongDA
c48306d117 feat: check signup item email regex in signup page (#2960)
* feat: check email regex in frontend

* Update SignupPage.js

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-05-19 22:07:34 +08:00
DacongDA
6efec6b4b5 feat: support "label" field for signin item table (#2956) 2024-05-19 03:07:36 +08:00
37 changed files with 294 additions and 64 deletions

View File

@@ -194,7 +194,7 @@ jobs:
with:
context: .
target: STANDARD
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
push: true
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
@@ -204,7 +204,7 @@ jobs:
with:
context: .
target: ALLINONE
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest

View File

@@ -1,10 +1,10 @@
FROM node:18.19.0 AS FRONT
FROM --platform=$BUILDPLATFORM node:18.19.0 AS FRONT
WORKDIR /web
COPY ./web .
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
FROM golang:1.20.12 AS BACK
FROM --platform=$BUILDPLATFORM golang:1.20.12 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN ./build.sh
@@ -13,6 +13,9 @@ RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go >
FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/"
ARG USER=casdoor
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --update sudo
@@ -28,7 +31,7 @@ RUN adduser -D $USER -u 1000 \
USER 1000
WORKDIR /
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server ./server
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
@@ -47,12 +50,15 @@ RUN apt update \
FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/"
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates
WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf

View File

@@ -8,4 +8,6 @@ else
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
export GOPROXY="https://goproxy.cn,direct"
fi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .

View File

@@ -25,6 +25,7 @@ import (
"time"
"github.com/casdoor/casdoor/util"
"github.com/nyaruka/phonenumbers"
"golang.org/x/oauth2"
)
@@ -130,6 +131,23 @@ type GoogleUserInfo struct {
Locale string `json:"locale"`
}
type GooglePeopleApiPhoneNumberMetaData struct {
Primary bool `json:"primary"`
}
type GooglePeopleApiPhoneNumber struct {
CanonicalForm string `json:"canonicalForm"`
MetaData GooglePeopleApiPhoneNumberMetaData `json:"metadata"`
Value string `json:"value"`
Type string `json:"type"`
}
type GooglePeopleApiResult struct {
PhoneNumbers []GooglePeopleApiPhoneNumber `json:"phoneNumbers"`
Etag string `json:"etag"`
ResourceName string `json:"resourceName"`
}
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) {
googleIdToken, ok := token.Extra(GoogleIdTokenKey).(GoogleIdToken)
@@ -167,12 +185,49 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, errors.New("google email is empty")
}
url = fmt.Sprintf("https://people.googleapis.com/v1/people/me?personFields=phoneNumbers&access_token=%s", token.AccessToken)
resp, err = idp.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var googlePeopleResult GooglePeopleApiResult
err = json.Unmarshal(body, &googlePeopleResult)
if err != nil {
return nil, err
}
var phoneNumber string
var countryCode string
if len(googlePeopleResult.PhoneNumbers) != 0 {
for _, phoneData := range googlePeopleResult.PhoneNumbers {
if phoneData.MetaData.Primary {
phoneNumber = phoneData.CanonicalForm
break
}
}
phoneNumberParsed, err := phonenumbers.Parse(phoneNumber, "")
if err != nil {
return nil, err
}
countryCode = phonenumbers.GetRegionCodeForNumber(phoneNumberParsed)
phoneNumber = fmt.Sprintf("%d", phoneNumberParsed.GetNationalNumber())
}
userInfo := UserInfo{
Id: googleUserInfo.Id,
Username: googleUserInfo.Email,
DisplayName: googleUserInfo.Name,
Email: googleUserInfo.Email,
AvatarUrl: googleUserInfo.Picture,
Phone: phoneNumber,
CountryCode: countryCode,
}
return &userInfo, nil
}

View File

@@ -46,6 +46,7 @@ type SigninItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Label string `json:"label"`
CustomCss string `json:"customCss"`
Placeholder string `json:"placeholder"`
Rule string `json:"rule"`
IsCustom bool `json:"isCustom"`
@@ -209,7 +210,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem := &SigninItem{
Name: "Back button",
Visible: true,
Label: ".back-button {\n top: 65px;\n left: 15px;\n position: absolute;\n}\n.back-inner-button{}",
CustomCss: ".back-button {\n top: 65px;\n left: 15px;\n position: absolute;\n}\n.back-inner-button{}",
Placeholder: "",
Rule: "None",
}
@@ -217,7 +218,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Languages",
Visible: true,
Label: ".login-languages {\n top: 55px;\n right: 5px;\n position: absolute;\n}",
CustomCss: ".login-languages {\n top: 55px;\n right: 5px;\n position: absolute;\n}",
Placeholder: "",
Rule: "None",
}
@@ -225,7 +226,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Logo",
Visible: true,
Label: ".login-logo-box {}",
CustomCss: ".login-logo-box {}",
Placeholder: "",
Rule: "None",
}
@@ -233,7 +234,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Signin methods",
Visible: true,
Label: ".signin-methods {}",
CustomCss: ".signin-methods {}",
Placeholder: "",
Rule: "None",
}
@@ -241,7 +242,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Username",
Visible: true,
Label: ".login-username {}\n.login-username-input{}",
CustomCss: ".login-username {}\n.login-username-input{}",
Placeholder: "",
Rule: "None",
}
@@ -249,7 +250,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Password",
Visible: true,
Label: ".login-password {}\n.login-password-input{}",
CustomCss: ".login-password {}\n.login-password-input{}",
Placeholder: "",
Rule: "None",
}
@@ -257,7 +258,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Agreement",
Visible: true,
Label: ".login-agreement {}",
CustomCss: ".login-agreement {}",
Placeholder: "",
Rule: "None",
}
@@ -265,7 +266,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Forgot password?",
Visible: true,
Label: ".login-forget-password {\n display: inline-flex;\n justify-content: space-between;\n width: 320px;\n margin-bottom: 25px;\n}",
CustomCss: ".login-forget-password {\n display: inline-flex;\n justify-content: space-between;\n width: 320px;\n margin-bottom: 25px;\n}",
Placeholder: "",
Rule: "None",
}
@@ -273,7 +274,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Login button",
Visible: true,
Label: ".login-button-box {\n margin-bottom: 5px;\n}\n.login-button {\n width: 100%;\n}",
CustomCss: ".login-button-box {\n margin-bottom: 5px;\n}\n.login-button {\n width: 100%;\n}",
Placeholder: "",
Rule: "None",
}
@@ -281,7 +282,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Signup link",
Visible: true,
Label: ".login-signup-link {\n margin-bottom: 24px;\n display: flex;\n justify-content: end;\n}",
CustomCss: ".login-signup-link {\n margin-bottom: 24px;\n display: flex;\n justify-content: end;\n}",
Placeholder: "",
Rule: "None",
}
@@ -289,12 +290,18 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{
Name: "Providers",
Visible: true,
Label: ".provider-img {\n width: 30px;\n margin: 5px;\n}\n.provider-big-img {\n margin-bottom: 10px;\n}",
CustomCss: ".provider-img {\n width: 30px;\n margin: 5px;\n}\n.provider-big-img {\n margin-bottom: 10px;\n}",
Placeholder: "",
Rule: "None",
}
application.SigninItems = append(application.SigninItems, signinItem)
}
for idx, item := range application.SigninItems {
if item.Label != "" && item.CustomCss == "" {
application.SigninItems[idx].CustomCss = item.Label
application.SigninItems[idx].Label = ""
}
}
return
}

View File

@@ -50,7 +50,7 @@ type Provider struct {
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode, if type is Google, it means sync phone number
Title string `xorm:"varchar(100)" json:"title"`
Content string `xorm:"varchar(2000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
Receiver string `xorm:"varchar(100)" json:"receiver"`

View File

@@ -169,6 +169,12 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.TotpSecret = value
case "SignupApplication":
user.SignupApplication = value
case "MfaPhoneEnabled":
user.MfaPhoneEnabled = util.ParseBool(value)
case "MfaEmailEnabled":
user.MfaEmailEnabled = util.ParseBool(value)
case "RecoveryCodes":
user.RecoveryCodes = strings.Split(value, ",")
}
}
@@ -303,6 +309,9 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
m["PreferredMfaType"] = user.PreferredMfaType
m["TotpSecret"] = user.TotpSecret
m["SignupApplication"] = user.SignupApplication
m["MfaPhoneEnabled"] = util.BoolToString(user.MfaPhoneEnabled)
m["MfaEmailEnabled"] = util.BoolToString(user.MfaEmailEnabled)
m["RecoveryCodes"] = strings.Join(user.RecoveryCodes, ",")
m2 := map[string]string{}
for _, tableColumn := range syncer.TableColumns {

View File

@@ -832,6 +832,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
)
}
{
this.state.provider.type !== "Google" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Get phone number"), i18next.t("provider:Get phone number - Tooltip"))} :
</Col>
<Col span={1} >
<Switch disabled={!this.state.provider.clientId} checked={this.state.provider.disableSsl} onChange={checked => {
this.updateProviderField("disableSsl", checked);
}} />
</Col>
</Row>
)
}
{
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
<Row style={{marginTop: "20px"}} >

View File

@@ -1468,7 +1468,7 @@ export function getUserCommonFields() {
return ["Owner", "Name", "CreatedTime", "UpdatedTime", "DeletedTime", "Id", "Type", "Password", "PasswordSalt", "DisplayName", "FirstName", "LastName", "Avatar", "PermanentAvatar",
"Email", "EmailVerified", "Phone", "Location", "Address", "Affiliation", "Title", "IdCardType", "IdCard", "Homepage", "Bio", "Tag", "Region",
"Language", "Gender", "Birthday", "Education", "Score", "Ranking", "IsDefaultAvatar", "IsOnline", "IsAdmin", "IsForbidden", "IsDeleted", "CreatedIp",
"PreferredMfaType", "TotpSecret", "SignupApplication"];
"PreferredMfaType", "TotpSecret", "SignupApplication", "RecoveryCodes", "MfaPhoneEnabled", "MfaEmailEnabled"];
}
export function getDefaultFooterContent() {

View File

@@ -202,7 +202,7 @@ class UserEditPage extends React.Component {
return value;
}
updateUserField(key, value) {
updateUserField(key, value, idx) {
if (this.props.account === null) {
return;
}
@@ -210,7 +210,15 @@ class UserEditPage extends React.Component {
value = this.parseUserField(key, value);
const user = this.state.user;
user[key] = value;
if (key === "address") {
if (!user[key]) {
user[key] = ["", ""];
}
user[key][idx] = value;
} else {
user[key] = value;
}
this.setState({
user: user,
});
@@ -501,16 +509,33 @@ class UserEditPage extends React.Component {
);
} else if (accountItem.name === "Address") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.address} onChange={e => {
this.updateUserField("address", e.target.value);
}} />
</Col>
</Row>
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
</Col>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
<span>{i18next.t("user:Address line") + " 1"}</span> :
</Col>
<Col span={20} >
<Input value={!this.state.user.address ? "" : this.state.user.address[0]} onChange={e => {
this.updateUserField("address", e.target.value, 0);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
</Col>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
<span>{i18next.t("user:Address line") + " 2"}</span> :
</Col>
<Col span={20} >
<Input value={!this.state.user.address ? "" : this.state.user.address[1]} onChange={e => {
this.updateUserField("address", e.target.value, 1);
}} />
</Col>
</Row>
</React.Fragment>
);
} else if (accountItem.name === "Affiliation") {
return (

View File

@@ -532,7 +532,7 @@ class LoginPage extends React.Component {
if (signinItem.name === "Logo") {
return (
<div className="login-logo-box">
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{
Setting.renderHelmet(application)
}
@@ -544,7 +544,7 @@ class LoginPage extends React.Component {
} else if (signinItem.name === "Back button") {
return (
<div className="back-button">
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{
this.renderBackButton()
}
@@ -562,14 +562,14 @@ class LoginPage extends React.Component {
return (
<div className="login-languages">
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<LanguageSelect languages={application.organizationObj.languages} />
</div>
);
} else if (signinItem.name === "Signin methods") {
return (
<div>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{this.renderMethodChoiceBox()}
</div>
)
@@ -577,10 +577,11 @@ class LoginPage extends React.Component {
} else if (signinItem.name === "Username") {
return (
<div>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<Form.Item
name="username"
className="login-username"
label={signinItem.label ? signinItem.label : null}
rules={[
{
required: true,
@@ -653,14 +654,14 @@ class LoginPage extends React.Component {
} else if (signinItem.name === "Password") {
return (
<div>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{this.renderPasswordOrCodeInput()}
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{this.renderPasswordOrCodeInput(signinItem)}
</div>
);
} else if (signinItem.name === "Forgot password?") {
return (
<div>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div className="login-forget-password">
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}}>
@@ -668,7 +669,7 @@ class LoginPage extends React.Component {
</Checkbox>
</Form.Item>
{
signinItem.visible ? Setting.renderForgetLink(application, i18next.t("login:Forgot password?")) : null
signinItem.visible ? Setting.renderForgetLink(application, signinItem.label ? signinItem.label : i18next.t("login:Forgot password?")) : null
}
</div>
</div>
@@ -678,7 +679,7 @@ class LoginPage extends React.Component {
} else if (signinItem.name === "Login button") {
return (
<Form.Item className="login-button-box">
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<Button
type="primary"
htmlType="submit"
@@ -687,7 +688,7 @@ class LoginPage extends React.Component {
{
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
i18next.t("login:Sign In")
signinItem.label ? signinItem.label : i18next.t("login:Sign In")
}
</Button>
{
@@ -722,7 +723,7 @@ class LoginPage extends React.Component {
return (
<div>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
<Form.Item>
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
@@ -737,13 +738,13 @@ class LoginPage extends React.Component {
);
} else if (signinItem.name.startsWith("Text ") || signinItem?.isCustom) {
return (
<div dangerouslySetInnerHTML={{__html: signinItem.label}} />
<div dangerouslySetInnerHTML={{__html: signinItem.customCss}} />
);
} else if (signinItem.name === "Signup link") {
return (
<div style={{width: "100%"}} className="login-signup-link">
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.label?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{this.renderFooter(application)}
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
{this.renderFooter(application, signinItem)}
</div>
);
}
@@ -896,17 +897,20 @@ class LoginPage extends React.Component {
/>;
}
renderFooter(application) {
renderFooter(application, signinItem) {
return (
<div>
{
!application.enableSignUp ? null : (
<React.Fragment>
{i18next.t("login:No account?")}&nbsp;
{
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
}
</React.Fragment>
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
(
<React.Fragment>
{i18next.t("login:No account?")}
{
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
}
</React.Fragment>
)
)
}
</div>
@@ -1022,7 +1026,7 @@ class LoginPage extends React.Component {
});
}
renderPasswordOrCodeInput() {
renderPasswordOrCodeInput(signinItem) {
const application = this.getApplicationObj();
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
return (
@@ -1031,6 +1035,7 @@ class LoginPage extends React.Component {
<Form.Item
name="password"
className="login-password"
label={signinItem.label ? signinItem.label : null}
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
>
<Input.Password

View File

@@ -384,8 +384,7 @@ export function getAuthUrl(application, provider, method, code) {
let endpoint = authInfo[provider.type].endpoint;
let redirectUri = `${window.location.origin}/callback`;
const scope = authInfo[provider.type].scope;
let scope = authInfo[provider.type].scope;
const isShortState = (provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger")) || (provider.type === "Twitter");
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
@@ -396,9 +395,11 @@ export function getAuthUrl(application, provider, method, code) {
}
} else if (provider.type === "Apple") {
redirectUri = `${window.location.origin}/api/callback`;
} else if (provider.type === "Google" && provider.disableSsl) {
scope += "+https://www.googleapis.com/auth/user.phonenumbers.read";
}
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "Facebook"
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|| provider.type === "Bitbucket" || provider.type === "Box" || provider.type === "CloudFoundry" || provider.type === "Dailymotion"
@@ -410,6 +411,8 @@ export function getAuthUrl(application, provider, method, code) {
|| provider.type === "Twitch" || provider.type === "Typetalk" || provider.type === "Uber" || provider.type === "VK" || provider.type === "Wepay"
|| provider.type === "Xero" || provider.type === "Yahoo" || provider.type === "Yammer" || provider.type === "Yandex" || provider.type === "Zoom") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
} else if (provider.type === "QQ") {
return `${endpoint}?response_type=code&client_id=${provider.clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}&scope=${encodeURIComponent(scope)}`;
} else if (provider.type === "AzureADB2C") {
return `https://${provider.domain}.b2clogin.com/${provider.domain}.onmicrosoft.com/${provider.appId}/oauth2/v2.0/authorize?client_id=${provider.clientId}&nonce=defaultNonce&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${scope}&response_type=code&state=${state}&prompt=login`;
} else if (provider.type === "DingTalk") {

View File

@@ -389,6 +389,14 @@ class SignupPage extends React.Component {
return Promise.reject(i18next.t("signup:The input is not valid Email!"));
}
if (signupItem.regex) {
const reg = new RegExp(signupItem.regex);
if (!reg.test(this.state.email)) {
this.setState({validEmail: false});
return Promise.reject(i18next.t("signup:The input Email doesn't match the signup item regex!"));
}
}
this.setState({validEmail: true});
return Promise.resolve();
},

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name des Hosts",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Die Eingabe ist keine Rechnungssteuer-ID!",
"The input is not invoice title!": "Der Eingabewert ist nicht die Rechnungsbezeichnung!",
"The input is not valid Email!": "Die Eingabe ist keine gültige E-Mail-Adresse!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Drittanbieter-Anmeldungen, die mit dem Benutzer verknüpft sind",
"Address": "Adresse",
"Address - Tooltip": "Wohnadresse",
"Address line": "Address line",
"Affiliation": "Zugehörigkeit",
"Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Anfitrión",
"Host - Tooltip": "Nombre del anfitrión",
"IdP": "IdP = Proveedor de Identidad",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "¡La entrada no es el ID fiscal de la factura!",
"The input is not invoice title!": "¡El entrada no es el título de la factura!",
"The input is not valid Email!": "¡La entrada no es un correo electrónico válido!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Accesos sociales ligados por el usuario",
"Address": "Dirección",
"Address - Tooltip": "Dirección residencial",
"Address line": "Address line",
"Affiliation": "Afiliación",
"Affiliation - Tooltip": "Empleador, como el nombre de una empresa u organización",
"Bio": "Bio - Biografía",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "L'adresse e-mail affichée comme expéditeur dans les e-mails envoyés",
"From name": "Nom de l'expéditeur",
"From name - Tooltip": "Le nom affiché comme expéditeur dans les e-mails envoyés",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Hôte",
"Host - Tooltip": "Nom d'hôte",
"IdP": "IdP (Identité Fournisseur)",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "L'entrée n'est pas l'identifiant fiscal de la facture !",
"The input is not invoice title!": "L'entrée n'est pas un nom ou une dénomination sociale !",
"The input is not valid Email!": "L'entrée n'est pas une adresse e-mail valide !",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Service de connexions tiers liés au compte",
"Address": "Adresse",
"Address - Tooltip": "Adresse résidentielle",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employeur, tel que le nom de l'entreprise ou de l'organisation",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Tuan rumah",
"Host - Tooltip": "Nama tuan rumah",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Input ini bukan Tax ID faktur!",
"The input is not invoice title!": "Masukan bukan judul faktur!",
"The input is not valid Email!": "Input yang dimasukkan bukan sesuai dengan format Email yang valid!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Masuk sosial yang terhubung oleh pengguna",
"Address": "Alamat",
"Address - Tooltip": "Alamat tempat tinggal",
"Address line": "Address line",
"Affiliation": "Afiliasi",
"Affiliation - Tooltip": "Pemberi Kerja, seperti nama perusahaan atau nama organisasi",
"Bio": "Bio: Biografi",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "ホスト",
"Host - Tooltip": "ホストの名前",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "入力されたものは請求書の税番号ではありません!",
"The input is not invoice title!": "インプットは請求書タイトルではありません!",
"The input is not valid Email!": "入力されたものは有効なメールではありません",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "ユーザーによってリンクされたソーシャルログイン",
"Address": "住所",
"Address - Tooltip": "住所",
"Address line": "Address line",
"Affiliation": "所属",
"Affiliation - Tooltip": "企業名や団体名などの雇用主",
"Bio": "バイオ技術",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "호스트",
"Host - Tooltip": "호스트의 이름",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "입력한 것은 송장 세금 ID가 아닙니다!",
"The input is not invoice title!": "입력값은 송장 제목이 아닙니다!",
"The input is not valid Email!": "입력 값은 유효한 이메일이 아닙니다!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "사용자가 연결한 소셜 로그인",
"Address": "주소",
"Address - Tooltip": "주거지 주소",
"Address line": "Address line",
"Affiliation": "소속",
"Affiliation - Tooltip": "고용주, 회사명 또는 조직명",
"Bio": "바이오",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Endereço de e-mail do remetente",
"From name": "Nome do remetente",
"From name - Tooltip": "Nome do remetente",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Nome do host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "A entrada não é um ID fiscal de fatura válido!",
"The input is not invoice title!": "A entrada não é um título de fatura válido!",
"The input is not valid Email!": "A entrada não é um Email válido!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Logins sociais vinculados pelo usuário",
"Address": "Endereço",
"Address - Tooltip": "Endereço residencial",
"Address line": "Address line",
"Affiliation": "Afiliação",
"Affiliation - Tooltip": "Empregador, como nome da empresa ou organização",
"Bio": "Biografia",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Хост",
"Host - Tooltip": "Имя хоста",
"IdP": "ИдП",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Входные данные не являются идентификатором налога по счету-фактуре!",
"The input is not invoice title!": "Входные данные не являются названием счета-фактуры!",
"The input is not valid Email!": "Ввод не является действительным адресом электронной почты!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Социальные логины, связанные пользователем",
"Address": "Адрес",
"Address - Tooltip": "Адрес проживания",
"Address line": "Address line",
"Affiliation": "Принадлежность",
"Affiliation - Tooltip": "Работодатель, такой как название компании или организации",
"Bio": "Био",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"",
"From name": "From name",
"From name - Tooltip": "Name of \"From\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Host",
"Host - Tooltip": "Name of host",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
"The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address",
"Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "Електронна адреса \"Від\"",
"From name": "Від імені",
"From name - Tooltip": "Назва \"Від\"",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Хост",
"Host - Tooltip": "Ім'я хоста",
"IdP": "IDP",
@@ -954,6 +956,7 @@
"Text 3": "Текст 3",
"Text 4": "Текст 4",
"Text 5": "Текст 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Введені дані не є ідентифікаційним номером платника податків!",
"The input is not invoice title!": "Введені дані не є назвою рахунку!",
"The input is not valid Email!": "Введена недійсна адреса електронної пошти!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Соціальні входи, пов’язані користувачем",
"Address": "Адреса",
"Address - Tooltip": "Адреса місця проживання",
"Address line": "Address line",
"Affiliation": "Приналежність",
"Affiliation - Tooltip": "Роботодавець, наприклад назва компанії чи організації",
"Bio": "біографія",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "If sync phone number is enabled, you should enable google people api first and add scope https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "Chủ nhà",
"Host - Tooltip": "Tên của người chủ chỗ ở",
"IdP": "IdP",
@@ -954,6 +956,7 @@
"Text 3": "Text 3",
"Text 4": "Text 4",
"Text 5": "Text 5",
"The input Email doesn't match the signup item regex!": "The input Email doesn't match the signup item regex!",
"The input is not invoice Tax ID!": "Đầu vào không phải là Mã số thuế của hóa đơn!",
"The input is not invoice title!": "Đầu vào không phải là tiêu đề hóa đơn!",
"The input is not valid Email!": "Đầu vào không phải là địa chỉ Email hợp lệ!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "Đăng nhập xã hội liên kết bởi người dùng",
"Address": "Địa chỉ",
"Address - Tooltip": "Địa chỉ cư trú",
"Address line": "Address line",
"Affiliation": "Liên kết",
"Affiliation - Tooltip": "Nhà tuyển dụng, chẳng hạn như tên công ty hoặc tổ chức",
"Bio": "bản vẻ đời sống",

View File

@@ -778,6 +778,8 @@
"From address - Tooltip": "邮件里发件人的邮箱地址",
"From name": "发件人名称",
"From name - Tooltip": "邮件里发件人的显示名称",
"Get phone number": "获取手机号码",
"Get phone number - Tooltip": "如果启用获取手机号码你需要先启用peopleApi并添加范围https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "主机",
"Host - Tooltip": "主机名",
"IdP": "身份提供商",
@@ -954,6 +956,7 @@
"Text 3": "文本3",
"Text 4": "文本4",
"Text 5": "文本5",
"The input Email doesn't match the signup item regex!": "您输入的邮箱地址与注册项的regex表达式不匹配!",
"The input is not invoice Tax ID!": "您输入的纳税人识别号有误!",
"The input is not invoice title!": "您输入的发票抬头有误!",
"The input is not valid Email!": "您输入的电子邮箱格式有误!",
@@ -1074,6 +1077,7 @@
"3rd-party logins - Tooltip": "用户所绑定的社会化登录",
"Address": "地址",
"Address - Tooltip": "居住地址",
"Address line": "地址",
"Affiliation": "工作单位",
"Affiliation - Tooltip": "工作单位,如公司、组织名称",
"Bio": "自我介绍",

View File

@@ -134,7 +134,7 @@ class SigninTable extends React.Component {
value={getItemDisplayName(text)}
onChange={value => {
this.updateField(table, index, "name", value);
this.updateField(table, index, "label", SigninTableDefaultCssMap[value]);
this.updateField(table, index, "customCss", SigninTableDefaultCssMap[value]);
}} >
{
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
@@ -166,7 +166,7 @@ class SigninTable extends React.Component {
},
},
{
title: i18next.t("signup:Label HTML"),
title: i18next.t("signup:Label"),
dataIndex: "label",
key: "label",
width: "200px",
@@ -188,14 +188,18 @@ class SigninTable extends React.Component {
}} />
</Popover>
);
} else if (["Username", "Password", "Signup link", "Forgot password?", "Login button"].includes(record.name)) {
return <Input value={text} style={{marginBottom: "10px"}} onChange={e => {
this.updateField(table, index, "label", e.target.value);
}} />;
}
return null;
},
},
{
title: i18next.t("application:Custom CSS"),
dataIndex: "label",
key: "label",
dataIndex: "customCss",
key: "customCss",
width: "200px",
render: (text, record, index) => {
if (!record.name.startsWith("Text ") && !record?.isCustom) {
@@ -205,13 +209,13 @@ class SigninTable extends React.Component {
<CodeMirror value={text?.replaceAll("<style>", "").replaceAll("</style>", "")}
options={{mode: "css", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateField(table, index, "label", value);
this.updateField(table, index, "customCss", value);
}}
/>
</div>
} title={i18next.t("application:CSS style")} trigger="click">
<Input value={text?.replaceAll("<style>", "").replaceAll("</style>", "")} onChange={e => {
this.updateField(table, index, "label", e.target.value);
this.updateField(table, index, "customCss", e.target.value);
}} />
</Popover>
);