Compare commits

...

26 Commits

Author SHA1 Message Date
e2cd0604c2 feat: add back arm64 support in Docker image (#2969) 2024-05-26 01:22:49 +08:00
78c3065fbb feat: fix address field bug in user edit page 2024-05-24 17:19:27 +08:00
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
bfcfb56336 feat: add address line 1 and 2 in web UI (#2961) 2024-05-19 23:55:38 +08:00
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
6efec6b4b5 feat: support "label" field for signin item table (#2956) 2024-05-19 03:07:36 +08:00
2daf26aa88 feat: use lowercase username when isUsernameLowered is enabled (#2952)
* feat: auto trim username during login and lowercase when isUsernameLowered enabled in conf

* fix: fix linter error

* fix: fix linter error

* fix: fix linter error
2024-05-17 11:43:19 +08:00
21c151bcf8 feat: fix password not updated bug when updating syncer (#2945) 2024-05-13 00:12:35 +08:00
b6b0b7d318 feat: support checking whether send-webhook API has error (#2944)
* feat: add webhook response for record

* refactor: refactor SendWebhook and use readall to read response body

* fix: improve code format

* fix: improve code format

* fix: improve code format
2024-05-12 20:30:15 +08:00
0ecc1d599f feat: fix bug in AddUsersInBatch() 2024-05-11 16:59:33 +08:00
3456fc6695 fix: update go-sms-sender to v0.23.0 2024-05-10 14:05:53 +08:00
c302dc7b8e fix: fix bug when init plan and pricing and record (#2934)
* fix: fix potential bugs in init data

* fix: improve code format

* fix: fix bug when init plan and pricing and record
2024-05-07 23:33:01 +08:00
d24ddd4f1c feat: fix potential bugs in init_data.go (#2932)
* fix: fix potential bugs in init data

* fix: improve code format
2024-05-07 23:11:08 +08:00
572616d390 fix: fix bug in ProviderItem.CountryCodes 2024-05-07 17:17:45 +08:00
2187310dbc feat: fix bug in initDefinedOrganization() 2024-05-06 13:57:08 +08:00
26345bb21b feat: add sms provider sendcloud (#2927) 2024-05-06 13:38:55 +08:00
e0455df504 feat: improve record content masking (#2923)
* feat: hide password in record

* feat: improve code format

* feat: improve code format
2024-05-05 12:42:09 +08:00
1dfbbf0e90 feat: fix bug that fails to import built-in org via init_data.json (#2922) 2024-05-05 01:06:15 +08:00
d43d58dee2 feat: fix getProviders() owner bug in product edit page 2024-05-01 18:04:50 +08:00
9eb4b12041 fix: rename to countryCodes for UI 2024-05-01 11:44:21 +08:00
3a45a4ee77 fix: rename to countryCodes 2024-05-01 09:47:44 +08:00
43393f034b feat: fix the Email provider fails to match bug in GetProviderByCategoryAndRule() 2024-05-01 09:44:19 +08:00
bafa80513b fix: improve ProviderTable column UI 2024-05-01 00:46:48 +08:00
8d08140421 fix: fix typo in initBuiltInPermission() 2024-05-01 00:41:16 +08:00
3d29e27d54 feat: support multiple SMS providers for different regions (#2914)
* feat: support using different sms provider for different region

* feat: add multiple support for select and remove log

* feat: revert change for countryCode in loginPage

* feat: revert change for countryCode in user_util.go

* feat: revert change for countryCode in auth.go

* Update application_item.go

* Update CountryCodeSelect.js

* Update ProviderTable.js

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-05-01 00:40:47 +08:00
199f1d4d10 feat: fix Auto-login causing AuthCodeWithPKCE Failures (#2911) 2024-04-30 12:14:50 +08:00
62 changed files with 792 additions and 192 deletions

View File

@ -194,7 +194,7 @@ jobs:
with: with:
context: . context: .
target: STANDARD target: STANDARD
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
@ -204,7 +204,7 @@ jobs:
with: with:
context: . context: .
target: ALLINONE target: ALLINONE
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest 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 WORKDIR /web
COPY ./web . COPY ./web .
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build 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 WORKDIR /go/src/casdoor
COPY . . COPY . .
RUN ./build.sh 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 FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
ARG USER=casdoor ARG USER=casdoor
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN sed -i 's/https/http/' /etc/apk/repositories RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --update sudo RUN apk add --update sudo
@ -28,7 +31,7 @@ RUN adduser -D $USER -u 1000 \
USER 1000 USER 1000
WORKDIR / 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/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/conf/app.conf ./conf/app.conf
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt 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 FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN apt update RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates RUN apt install -y ca-certificates && update-ca-certificates
WORKDIR / 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/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf 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" echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
export GOPROXY="https://goproxy.cn,direct" export GOPROXY="https://goproxy.cn,direct"
fi 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

@ -20,6 +20,7 @@ import (
"strings" "strings"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -293,6 +294,11 @@ func (c *ApiController) UpdateUser() {
return return
} }
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
if isUsernameLowered {
user.Name = strings.ToLower(user.Name)
}
isAdmin := c.IsAdmin() isAdmin := c.IsAdmin()
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass { if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass {
c.ResponseError(err) c.ResponseError(err)

View File

@ -295,7 +295,7 @@ func (c *ApiController) SendVerificationCode() {
vform.CountryCode = mfaProps.CountryCode vform.CountryCode = mfaProps.CountryCode
} }
provider, err = application.GetSmsProvider(vform.Method) provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

6
go.mod
View File

@ -9,12 +9,12 @@ require (
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.77.2 github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.20.0 github.com/casdoor/go-sms-sender v0.23.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.45.0 github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.6.0 github.com/casdoor/oss v1.6.0
github.com/casdoor/xorm-adapter/v3 v3.1.0 github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.3.0 github.com/casvisor/casvisor-go-sdk v1.4.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0 github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
@ -45,7 +45,7 @@ require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0 github.com/russellhaering/goxmldsig v1.2.0
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect github.com/sendgrid/sendgrid-go v3.14.0+incompatible
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed

10
go.sum
View File

@ -1003,6 +1003,8 @@ github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0I
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apistd/uni-go-sdk v0.0.2 h1:7kqETCOz/rz8AQU55XGzxDFGoFeMgeZL5fGwvxKBZrc=
github.com/apistd/uni-go-sdk v0.0.2/go.mod h1:eIqYos4IbHgE/rB75r05ypNLahooEMJCrbjXq322b74=
github.com/appleboy/go-fcm v0.1.5/go.mod h1:MSxZ4LqGRsnywOjnlXJXMqbjZrG4vf+0oHitfC9HRH0= github.com/appleboy/go-fcm v0.1.5/go.mod h1:MSxZ4LqGRsnywOjnlXJXMqbjZrG4vf+0oHitfC9HRH0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@ -1083,8 +1085,8 @@ github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk= github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs= github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc= github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
github.com/casdoor/go-sms-sender v0.20.0 h1:yLbCakV04DzzehhgBklOrSeCFjMwpfKBeemz9b+Y8OM= github.com/casdoor/go-sms-sender v0.23.0 h1:N8+By4JNwyilEcx7cp0QGOepafefM88VwV+o3UEFZio=
github.com/casdoor/go-sms-sender v0.20.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs= github.com/casdoor/go-sms-sender v0.23.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w= github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q= github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk= github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
@ -1093,8 +1095,8 @@ github.com/casdoor/oss v1.6.0 h1:IOWrGLJ+VO82qS796eaRnzFPPA1Sn3cotYTi7O/VIlQ=
github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE= github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk= github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM= github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
github.com/casvisor/casvisor-go-sdk v1.3.0 h1:HVgm2g3lWpNX2wBNidzR743QY4O5kAjLUJ9tS2juO8g= github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
github.com/casvisor/casvisor-go-sdk v1.3.0/go.mod h1:frnNtH5GA0wxzAQLyZxxfL0RSsSub9GQPi2Ybe86ocE= github.com/casvisor/casvisor-go-sdk v1.4.0/go.mod h1:frnNtH5GA0wxzAQLyZxxfL0RSsSub9GQPi2Ybe86ocE=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=

View File

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/nyaruka/phonenumbers"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -130,6 +131,23 @@ type GoogleUserInfo struct {
Locale string `json:"locale"` 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) { func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) { if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) {
googleIdToken, ok := token.Extra(GoogleIdTokenKey).(GoogleIdToken) 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") 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{ userInfo := UserInfo{
Id: googleUserInfo.Id, Id: googleUserInfo.Id,
Username: googleUserInfo.Email, Username: googleUserInfo.Email,
DisplayName: googleUserInfo.Name, DisplayName: googleUserInfo.Name,
Email: googleUserInfo.Email, Email: googleUserInfo.Email,
AvatarUrl: googleUserInfo.Picture, AvatarUrl: googleUserInfo.Picture,
Phone: phoneNumber,
CountryCode: countryCode,
} }
return &userInfo, nil return &userInfo, nil
} }

View File

@ -46,6 +46,7 @@ type SigninItem struct {
Name string `json:"name"` Name string `json:"name"`
Visible bool `json:"visible"` Visible bool `json:"visible"`
Label string `json:"label"` Label string `json:"label"`
CustomCss string `json:"customCss"`
Placeholder string `json:"placeholder"` Placeholder string `json:"placeholder"`
Rule string `json:"rule"` Rule string `json:"rule"`
IsCustom bool `json:"isCustom"` IsCustom bool `json:"isCustom"`
@ -209,7 +210,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem := &SigninItem{ signinItem := &SigninItem{
Name: "Back button", Name: "Back button",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -217,7 +218,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Languages", Name: "Languages",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -225,7 +226,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Logo", Name: "Logo",
Visible: true, Visible: true,
Label: ".login-logo-box {}", CustomCss: ".login-logo-box {}",
Placeholder: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -233,7 +234,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Signin methods", Name: "Signin methods",
Visible: true, Visible: true,
Label: ".signin-methods {}", CustomCss: ".signin-methods {}",
Placeholder: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -241,7 +242,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Username", Name: "Username",
Visible: true, Visible: true,
Label: ".login-username {}\n.login-username-input{}", CustomCss: ".login-username {}\n.login-username-input{}",
Placeholder: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -249,7 +250,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Password", Name: "Password",
Visible: true, Visible: true,
Label: ".login-password {}\n.login-password-input{}", CustomCss: ".login-password {}\n.login-password-input{}",
Placeholder: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -257,7 +258,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Agreement", Name: "Agreement",
Visible: true, Visible: true,
Label: ".login-agreement {}", CustomCss: ".login-agreement {}",
Placeholder: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -265,7 +266,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Forgot password?", Name: "Forgot password?",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -273,7 +274,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Login button", Name: "Login button",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -281,7 +282,7 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Signup link", Name: "Signup link",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
@ -289,12 +290,18 @@ func extendApplicationWithSigninItems(application *Application) (err error) {
signinItem = &SigninItem{ signinItem = &SigninItem{
Name: "Providers", Name: "Providers",
Visible: true, 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: "", Placeholder: "",
Rule: "None", Rule: "None",
} }
application.SigninItems = append(application.SigninItems, signinItem) 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 return
} }
@ -670,11 +677,7 @@ func AddApplication(application *Application) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func DeleteApplication(application *Application) (bool, error) { func deleteApplication(application *Application) (bool, error) {
if application.Name == "app-built-in" {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{}) affected, err := ormer.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
if err != nil { if err != nil {
return false, err return false, err
@ -683,6 +686,14 @@ func DeleteApplication(application *Application) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func DeleteApplication(application *Application) (bool, error) {
if application.Name == "app-built-in" {
return false, nil
}
return deleteApplication(application)
}
func (application *Application) GetId() string { func (application *Application) GetId() string {
return fmt.Sprintf("%s/%s", application.Owner, application.Name) return fmt.Sprintf("%s/%s", application.Owner, application.Name)
} }

View File

@ -38,7 +38,20 @@ func (application *Application) GetProviderByCategory(category string) (*Provide
return nil, nil return nil, nil
} }
func (application *Application) GetProviderByCategoryAndRule(category string, method string) (*Provider, error) { func isProviderItemCountryCodeMatched(providerItem *ProviderItem, countryCode string) bool {
if len(providerItem.CountryCodes) == 0 {
return true
}
for _, countryCode2 := range providerItem.CountryCodes {
if countryCode2 == "" || countryCode2 == "All" || countryCode2 == "all" || countryCode2 == countryCode {
return true
}
}
return false
}
func (application *Application) GetProviderByCategoryAndRule(category string, method string, countryCode string) (*Provider, error) {
providers, err := GetProviders(application.Organization) providers, err := GetProviders(application.Organization)
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,7 +67,13 @@ func (application *Application) GetProviderByCategoryAndRule(category string, me
} }
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
if providerItem.Rule == method || (providerItem.Rule == "all" || providerItem.Rule == "" || providerItem.Rule == "None") { if providerItem.Provider != nil && providerItem.Provider.Category == "SMS" {
if !isProviderItemCountryCodeMatched(providerItem, countryCode) {
continue
}
}
if providerItem.Rule == method || providerItem.Rule == "" || providerItem.Rule == "All" || providerItem.Rule == "all" || providerItem.Rule == "None" {
if provider, ok := m[providerItem.Name]; ok { if provider, ok := m[providerItem.Name]; ok {
return provider, nil return provider, nil
} }
@ -65,11 +84,11 @@ func (application *Application) GetProviderByCategoryAndRule(category string, me
} }
func (application *Application) GetEmailProvider(method string) (*Provider, error) { func (application *Application) GetEmailProvider(method string) (*Provider, error) {
return application.GetProviderByCategoryAndRule("Email", method) return application.GetProviderByCategoryAndRule("Email", method, "All")
} }
func (application *Application) GetSmsProvider(method string) (*Provider, error) { func (application *Application) GetSmsProvider(method string, countryCode string) (*Provider, error) {
return application.GetProviderByCategoryAndRule("SMS", method) return application.GetProviderByCategoryAndRule("SMS", method, countryCode)
} }
func (application *Application) GetStorageProvider() (*Provider, error) { func (application *Application) GetStorageProvider() (*Provider, error) {

View File

@ -154,6 +154,15 @@ func AddGroups(groups []*Group) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func deleteGroup(group *Group) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteGroup(group *Group) (bool, error) { func DeleteGroup(group *Group) (bool, error) {
_, err := ormer.Engine.Get(group) _, err := ormer.Engine.Get(group)
if err != nil { if err != nil {
@ -172,12 +181,7 @@ func DeleteGroup(group *Group) (bool, error) {
return false, errors.New("group has users") return false, errors.New("group has users")
} }
affected, err := ormer.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{}) return deleteGroup(group)
if err != nil {
return false, err
}
return affected != 0, nil
} }
func checkGroupName(name string) error { func checkGroupName(name string) error {

View File

@ -409,7 +409,7 @@ func initBuiltInPermission() {
Groups: []string{}, Groups: []string{},
Roles: []string{}, Roles: []string{},
Domains: []string{}, Domains: []string{},
Model: "model-built-in", Model: "user-model-built-in",
Adapter: "", Adapter: "",
ResourceType: "Application", ResourceType: "Application",
Resources: []string{"app-built-in"}, Resources: []string{"app-built-in"},

View File

@ -266,7 +266,13 @@ func initDefinedOrganization(organization *Organization) {
} }
if existed != nil { if existed != nil {
return affected, err := deleteOrganization(organization)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete organization")
}
} }
organization.CreatedTime = util.GetCurrentTime() organization.CreatedTime = util.GetCurrentTime()
organization.AccountItems = getBuiltInAccountItems() organization.AccountItems = getBuiltInAccountItems()
@ -284,7 +290,13 @@ func initDefinedApplication(application *Application) {
} }
if existed != nil { if existed != nil {
return affected, err := deleteApplication(application)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete application")
}
} }
application.CreatedTime = util.GetCurrentTime() application.CreatedTime = util.GetCurrentTime()
_, err = AddApplication(application) _, err = AddApplication(application)
@ -299,7 +311,13 @@ func initDefinedUser(user *User) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := deleteUser(user)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete user")
}
} }
user.CreatedTime = util.GetCurrentTime() user.CreatedTime = util.GetCurrentTime()
user.Id = util.GenerateId() user.Id = util.GenerateId()
@ -319,7 +337,13 @@ func initDefinedCert(cert *Cert) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteCert(cert)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete cert")
}
} }
cert.CreatedTime = util.GetCurrentTime() cert.CreatedTime = util.GetCurrentTime()
_, err = AddCert(cert) _, err = AddCert(cert)
@ -335,7 +359,13 @@ func initDefinedLdap(ldap *Ldap) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteLdap(ldap)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete ldap")
}
} }
_, err = AddLdap(ldap) _, err = AddLdap(ldap)
if err != nil { if err != nil {
@ -350,7 +380,13 @@ func initDefinedProvider(provider *Provider) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteProvider(provider)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete provider")
}
} }
_, err = AddProvider(provider) _, err = AddProvider(provider)
if err != nil { if err != nil {
@ -365,7 +401,13 @@ func initDefinedModel(model *Model) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteModel(model)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete provider")
}
} }
model.CreatedTime = util.GetCurrentTime() model.CreatedTime = util.GetCurrentTime()
_, err = AddModel(model) _, err = AddModel(model)
@ -381,7 +423,13 @@ func initDefinedPermission(permission *Permission) {
} }
if existed != nil { if existed != nil {
return affected, err := deletePermission(permission)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete permission")
}
} }
permission.CreatedTime = util.GetCurrentTime() permission.CreatedTime = util.GetCurrentTime()
_, err = AddPermission(permission) _, err = AddPermission(permission)
@ -397,7 +445,13 @@ func initDefinedPayment(payment *Payment) {
} }
if existed != nil { if existed != nil {
return affected, err := DeletePayment(payment)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete payment")
}
} }
payment.CreatedTime = util.GetCurrentTime() payment.CreatedTime = util.GetCurrentTime()
_, err = AddPayment(payment) _, err = AddPayment(payment)
@ -413,7 +467,13 @@ func initDefinedProduct(product *Product) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteProduct(product)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete product")
}
} }
product.CreatedTime = util.GetCurrentTime() product.CreatedTime = util.GetCurrentTime()
_, err = AddProduct(product) _, err = AddProduct(product)
@ -429,7 +489,13 @@ func initDefinedResource(resource *Resource) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteResource(resource)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete resource")
}
} }
resource.CreatedTime = util.GetCurrentTime() resource.CreatedTime = util.GetCurrentTime()
_, err = AddResource(resource) _, err = AddResource(resource)
@ -445,7 +511,13 @@ func initDefinedRole(role *Role) {
} }
if existed != nil { if existed != nil {
return affected, err := deleteRole(role)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete role")
}
} }
role.CreatedTime = util.GetCurrentTime() role.CreatedTime = util.GetCurrentTime()
_, err = AddRole(role) _, err = AddRole(role)
@ -461,7 +533,13 @@ func initDefinedSyncer(syncer *Syncer) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteSyncer(syncer)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete role")
}
} }
syncer.CreatedTime = util.GetCurrentTime() syncer.CreatedTime = util.GetCurrentTime()
_, err = AddSyncer(syncer) _, err = AddSyncer(syncer)
@ -477,7 +555,13 @@ func initDefinedToken(token *Token) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteToken(token)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete token")
}
} }
token.CreatedTime = util.GetCurrentTime() token.CreatedTime = util.GetCurrentTime()
_, err = AddToken(token) _, err = AddToken(token)
@ -493,7 +577,13 @@ func initDefinedWebhook(webhook *Webhook) {
} }
if existed != nil { if existed != nil {
return affected, err := DeleteWebhook(webhook)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete webhook")
}
} }
webhook.CreatedTime = util.GetCurrentTime() webhook.CreatedTime = util.GetCurrentTime()
_, err = AddWebhook(webhook) _, err = AddWebhook(webhook)
@ -508,7 +598,13 @@ func initDefinedGroup(group *Group) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := deleteGroup(group)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete group")
}
} }
group.CreatedTime = util.GetCurrentTime() group.CreatedTime = util.GetCurrentTime()
_, err = AddGroup(group) _, err = AddGroup(group)
@ -523,7 +619,13 @@ func initDefinedAdapter(adapter *Adapter) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeleteAdapter(adapter)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete adapter")
}
} }
adapter.CreatedTime = util.GetCurrentTime() adapter.CreatedTime = util.GetCurrentTime()
_, err = AddAdapter(adapter) _, err = AddAdapter(adapter)
@ -538,7 +640,13 @@ func initDefinedEnforcer(enforcer *Enforcer) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeleteEnforcer(enforcer)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete enforcer")
}
} }
enforcer.CreatedTime = util.GetCurrentTime() enforcer.CreatedTime = util.GetCurrentTime()
_, err = AddEnforcer(enforcer) _, err = AddEnforcer(enforcer)
@ -553,7 +661,13 @@ func initDefinedPlan(plan *Plan) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeletePlan(plan)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete plan")
}
} }
plan.CreatedTime = util.GetCurrentTime() plan.CreatedTime = util.GetCurrentTime()
_, err = AddPlan(plan) _, err = AddPlan(plan)
@ -563,12 +677,18 @@ func initDefinedPlan(plan *Plan) {
} }
func initDefinedPricing(pricing *Pricing) { func initDefinedPricing(pricing *Pricing) {
existed, err := getPlan(pricing.Owner, pricing.Name) existed, err := getPricing(pricing.Owner, pricing.Name)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeletePricing(pricing)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete pricing")
}
} }
pricing.CreatedTime = util.GetCurrentTime() pricing.CreatedTime = util.GetCurrentTime()
_, err = AddPricing(pricing) _, err = AddPricing(pricing)
@ -583,7 +703,13 @@ func initDefinedInvitation(invitation *Invitation) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeleteInvitation(invitation)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete invitation")
}
} }
invitation.CreatedTime = util.GetCurrentTime() invitation.CreatedTime = util.GetCurrentTime()
_, err = AddInvitation(invitation, "en") _, err = AddInvitation(invitation, "en")
@ -593,6 +719,7 @@ func initDefinedInvitation(invitation *Invitation) {
} }
func initDefinedRecord(record *casvisorsdk.Record) { func initDefinedRecord(record *casvisorsdk.Record) {
record.Id = 0
record.CreatedTime = util.GetCurrentTime() record.CreatedTime = util.GetCurrentTime()
_ = AddRecord(record) _ = AddRecord(record)
} }
@ -611,7 +738,13 @@ func initDefinedSubscription(subscription *Subscription) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeleteSubscription(subscription)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete subscription")
}
} }
subscription.CreatedTime = util.GetCurrentTime() subscription.CreatedTime = util.GetCurrentTime()
_, err = AddSubscription(subscription) _, err = AddSubscription(subscription)
@ -626,7 +759,13 @@ func initDefinedTransaction(transaction *Transaction) {
panic(err) panic(err)
} }
if existed != nil { if existed != nil {
return affected, err := DeleteTransaction(transaction)
if err != nil {
panic(err)
}
if !affected {
panic("Fail to delete transaction")
}
} }
transaction.CreatedTime = util.GetCurrentTime() transaction.CreatedTime = util.GetCurrentTime()
_, err = AddTransaction(transaction) _, err = AddTransaction(transaction)

View File

@ -241,11 +241,7 @@ func AddOrganization(organization *Organization) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func DeleteOrganization(organization *Organization) (bool, error) { func deleteOrganization(organization *Organization) (bool, error) {
if organization.Name == "built-in" {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{}) affected, err := ormer.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
if err != nil { if err != nil {
return false, err return false, err
@ -254,6 +250,14 @@ func DeleteOrganization(organization *Organization) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func DeleteOrganization(organization *Organization) (bool, error) {
if organization.Name == "built-in" {
return false, nil
}
return deleteOrganization(organization)
}
func GetOrganizationByUser(user *User) (*Organization, error) { func GetOrganizationByUser(user *User) (*Organization, error) {
if user == nil { if user == nil {
return nil, nil return nil, nil

View File

@ -286,13 +286,22 @@ func AddPermissionsInBatch(permissions []*Permission) (bool, error) {
return affected, nil return affected, nil
} }
func DeletePermission(permission *Permission) (bool, error) { func deletePermission(permission *Permission) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{}) affected, err := ormer.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
if err != nil { if err != nil {
return false, err return false, err
} }
if affected != 0 { return affected != 0, nil
}
func DeletePermission(permission *Permission) (bool, error) {
affected, err := deletePermission(permission)
if err != nil {
return false, err
}
if affected {
err = removeGroupingPolicies(permission) err = removeGroupingPolicies(permission)
if err != nil { if err != nil {
return false, err return false, err
@ -314,7 +323,7 @@ func DeletePermission(permission *Permission) (bool, error) {
} }
} }
return affected != 0, nil return affected, nil
} }
func getPermissionsByUser(userId string) ([]*Permission, error) { func getPermissionsByUser(userId string) ([]*Permission, error) {

View File

@ -133,7 +133,7 @@ func AddPlan(plan *Plan) (bool, error) {
} }
func DeletePlan(plan *Plan) (bool, error) { func DeletePlan(plan *Plan) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{plan.Owner, plan.Name}).Delete(plan) affected, err := ormer.Engine.ID(core.PK{plan.Owner, plan.Name}).Delete(Plan{})
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -140,7 +140,7 @@ func AddPricing(pricing *Pricing) (bool, error) {
} }
func DeletePricing(pricing *Pricing) (bool, error) { func DeletePricing(pricing *Pricing) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{pricing.Owner, pricing.Name}).Delete(pricing) affected, err := ormer.Engine.ID(core.PK{pricing.Owner, pricing.Name}).Delete(Pricing{})
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -50,7 +50,7 @@ type Provider struct {
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` 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"` 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 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"` Receiver string `xorm:"varchar(100)" json:"receiver"`

View File

@ -18,13 +18,14 @@ type ProviderItem struct {
Owner string `json:"owner"` Owner string `json:"owner"`
Name string `json:"name"` Name string `json:"name"`
CanSignUp bool `json:"canSignUp"` CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"` CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"` CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"` CountryCodes []string `json:"countryCodes"`
SignupGroup string `json:"signupGroup"` Prompted bool `json:"prompted"`
Rule string `json:"rule"` SignupGroup string `json:"signupGroup"`
Provider *Provider `json:"provider"` Rule string `json:"rule"`
Provider *Provider `json:"provider"`
} }
func (application *Application) GetProviderItem(providerName string) *ProviderItem { func (application *Application) GetProviderItem(providerName string) *ProviderItem {

View File

@ -17,6 +17,7 @@ package object
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/beego/beego/context" "github.com/beego/beego/context"
@ -25,10 +26,14 @@ import (
"github.com/casvisor/casvisor-go-sdk/casvisorsdk" "github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
var logPostOnly bool var (
logPostOnly bool
passwordRegex *regexp.Regexp
)
func init() { func init() {
logPostOnly = conf.GetConfigBool("logPostOnly") logPostOnly = conf.GetConfigBool("logPostOnly")
passwordRegex = regexp.MustCompile("\"password\":\".+\"")
} }
type Record struct { type Record struct {
@ -40,6 +45,10 @@ type Response struct {
Msg string `json:"msg"` Msg string `json:"msg"`
} }
func maskPassword(recordString string) string {
return passwordRegex.ReplaceAllString(recordString, "\"password\":\"***\"")
}
func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) { func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1) ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1) action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
@ -51,6 +60,7 @@ func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
object := "" object := ""
if ctx.Input.RequestBody != nil && len(ctx.Input.RequestBody) != 0 { if ctx.Input.RequestBody != nil && len(ctx.Input.RequestBody) != 0 {
object = string(ctx.Input.RequestBody) object = string(ctx.Input.RequestBody)
object = maskPassword(object)
} }
respBytes, err := json.Marshal(ctx.Input.Data()["json"]) respBytes, err := json.Marshal(ctx.Input.Data()["json"])
@ -80,12 +90,18 @@ func NewRecord(ctx *context.Context) (*casvisorsdk.Record, error) {
Action: action, Action: action,
Language: languageCode, Language: languageCode,
Object: object, Object: object,
StatusCode: 200,
Response: fmt.Sprintf("{status:\"%s\", msg:\"%s\"}", resp.Status, resp.Msg), Response: fmt.Sprintf("{status:\"%s\", msg:\"%s\"}", resp.Status, resp.Msg),
IsTriggered: false, IsTriggered: false,
} }
return &record, nil return &record, nil
} }
func addRecord(record *casvisorsdk.Record) (int64, error) {
affected, err := ormer.Engine.Insert(record)
return affected, err
}
func AddRecord(record *casvisorsdk.Record) bool { func AddRecord(record *casvisorsdk.Record) bool {
if logPostOnly { if logPostOnly {
if record.Method == "GET" { if record.Method == "GET" {
@ -98,6 +114,7 @@ func AddRecord(record *casvisorsdk.Record) bool {
} }
record.Owner = record.Organization record.Owner = record.Organization
record.Object = maskPassword(record.Object)
errWebhook := SendWebhooks(record) errWebhook := SendWebhooks(record)
if errWebhook == nil { if errWebhook == nil {
@ -107,7 +124,7 @@ func AddRecord(record *casvisorsdk.Record) bool {
} }
if casvisorsdk.GetClient() == nil { if casvisorsdk.GetClient() == nil {
affected, err := ormer.Engine.Insert(record) affected, err := addRecord(record)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -212,6 +229,40 @@ func getFilteredWebhooks(webhooks []*Webhook, organization string, action string
return res return res
} }
func addWebhookRecord(webhook *Webhook, record *casvisorsdk.Record, statusCode int, respBody string, sendError error) error {
if statusCode == 200 {
return nil
}
if len(respBody) > 300 {
respBody = respBody[0:300]
}
webhookRecord := &casvisorsdk.Record{
Owner: record.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Organization: record.Organization,
User: record.User,
Method: webhook.Method,
Action: "send-webhook",
RequestUri: webhook.Url,
StatusCode: statusCode,
Response: respBody,
Language: record.Language,
IsTriggered: false,
}
if sendError != nil {
webhookRecord.Response = sendError.Error()
}
_, err := addRecord(webhookRecord)
return err
}
func SendWebhooks(record *casvisorsdk.Record) error { func SendWebhooks(record *casvisorsdk.Record) error {
webhooks, err := getWebhooksByOrganization("") webhooks, err := getWebhooksByOrganization("")
if err != nil { if err != nil {
@ -236,11 +287,16 @@ func SendWebhooks(record *casvisorsdk.Record) error {
} }
} }
err = sendWebhook(webhook, record, user) statusCode, respBody, err := sendWebhook(webhook, record, user)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
continue
} }
err = addWebhookRecord(webhook, record, statusCode, respBody, err)
if err != nil {
errs = append(errs, err)
}
} }
if len(errs) > 0 { if len(errs) > 0 {

View File

@ -238,6 +238,15 @@ func AddRolesInBatch(roles []*Role) bool {
return affected return affected
} }
func deleteRole(role *Role) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteRole(role *Role) (bool, error) { func DeleteRole(role *Role) (bool, error) {
roleId := role.GetId() roleId := role.GetId()
permissions, err := GetPermissionsByRole(roleId) permissions, err := GetPermissionsByRole(roleId)
@ -253,12 +262,7 @@ func DeleteRole(role *Role) (bool, error) {
} }
} }
affected, err := ormer.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{}) return deleteRole(role)
if err != nil {
return false, err
}
return affected != 0, nil
} }
func (role *Role) GetId() string { func (role *Role) GetId() string {

View File

@ -48,7 +48,7 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
if provider.AppId != "" { if provider.AppId != "" {
phoneNumbers = append([]string{provider.AppId}, phoneNumbers...) phoneNumbers = append([]string{provider.AppId}, phoneNumbers...)
} }
} else if provider.Type == sender.Aliyun { } else if provider.Type == sender.Aliyun || provider.Type == sender.SendCloud {
for i, number := range phoneNumbers { for i, number := range phoneNumbers {
phoneNumbers[i] = strings.TrimPrefix(number, "+86") phoneNumbers[i] = strings.TrimPrefix(number, "+86")
} }

View File

@ -155,7 +155,8 @@ func GetMaskedSyncers(syncers []*Syncer, errs ...error) ([]*Syncer, error) {
func UpdateSyncer(id string, syncer *Syncer) (bool, error) { func UpdateSyncer(id string, syncer *Syncer) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if s, err := getSyncer(owner, name); err != nil { s, err := getSyncer(owner, name)
if err != nil {
return false, err return false, err
} else if s == nil { } else if s == nil {
return false, nil return false, nil
@ -163,7 +164,7 @@ func UpdateSyncer(id string, syncer *Syncer) (bool, error) {
session := ormer.Engine.ID(core.PK{owner, name}).AllCols() session := ormer.Engine.ID(core.PK{owner, name}).AllCols()
if syncer.Password == "***" { if syncer.Password == "***" {
session.Omit("password") syncer.Password = s.Password
} }
affected, err := session.Update(syncer) affected, err := session.Update(syncer)
if err != nil { if err != nil {

View File

@ -142,9 +142,11 @@ func (syncer *Syncer) syncUsers() error {
} }
} }
_, err = AddUsersInBatch(newUsers) if len(newUsers) != 0 {
if err != nil { _, err = AddUsersInBatch(newUsers)
return err if err != nil {
return err
}
} }
if !syncer.IsReadOnly { if !syncer.IsReadOnly {

View File

@ -877,6 +877,7 @@ func AddUsers(users []*User) (bool, error) {
} }
} }
user.Name = strings.TrimSpace(user.Name)
if isUsernameLowered { if isUsernameLowered {
user.Name = strings.ToLower(user.Name) user.Name = strings.ToLower(user.Name)
} }
@ -919,6 +920,15 @@ func AddUsersInBatch(users []*User) (bool, error) {
return affected, nil return affected, nil
} }
func deleteUser(user *User) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteUser(user *User) (bool, error) { func DeleteUser(user *User) (bool, error) {
// Forced offline the user first // Forced offline the user first
_, err := DeleteSession(util.GetSessionId(user.Owner, user.Name, CasdoorApplication)) _, err := DeleteSession(util.GetSessionId(user.Owner, user.Name, CasdoorApplication))
@ -926,12 +936,7 @@ func DeleteUser(user *User) (bool, error) {
return false, err return false, err
} }
affected, err := ormer.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{}) return deleteUser(user)
if err != nil {
return false, err
}
return affected != 0, nil
} }
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) { func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {

View File

@ -21,12 +21,11 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
jsoniter "github.com/json-iterator/go"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
jsoniter "github.com/json-iterator/go"
"github.com/xorm-io/core" "github.com/xorm-io/core"
) )
@ -57,6 +56,13 @@ func HasUserByField(organizationName string, field string, value string) bool {
} }
func GetUserByFields(organization string, field string) (*User, error) { func GetUserByFields(organization string, field string) (*User, error) {
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
if isUsernameLowered {
field = strings.ToLower(field)
}
field = strings.TrimSpace(field)
// check username // check username
user, err := GetUserByField(organization, "name", field) user, err := GetUserByField(organization, "name", field)
if err != nil || user != nil { if err != nil || user != nil {

View File

@ -15,6 +15,7 @@
package object package object
import ( import (
"io"
"net/http" "net/http"
"strings" "strings"
@ -22,7 +23,7 @@ import (
"github.com/casvisor/casvisor-go-sdk/casvisorsdk" "github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *User) error { func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *User) (int, string, error) {
client := &http.Client{} client := &http.Client{}
type RecordEx struct { type RecordEx struct {
@ -38,7 +39,7 @@ func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *Use
req, err := http.NewRequest(webhook.Method, webhook.Url, body) req, err := http.NewRequest(webhook.Method, webhook.Url, body)
if err != nil { if err != nil {
return err return 0, "", err
} }
req.Header.Set("Content-Type", webhook.ContentType) req.Header.Set("Content-Type", webhook.ContentType)
@ -47,6 +48,15 @@ func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *Use
req.Header.Set(header.Name, header.Value) req.Header.Set(header.Name, header.Value)
} }
_, err = client.Do(req) resp, err := client.Do(req)
return err if err != nil {
return 0, "", err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return 0, "", err
}
return resp.StatusCode, string(bodyBytes), err
} }

View File

@ -59,7 +59,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
scope := ctx.Input.Query("scope") scope := ctx.Input.Query("scope")
state := ctx.Input.Query("state") state := ctx.Input.Query("state")
nonce := "" nonce := ""
codeChallenge := "" codeChallenge := ctx.Input.Query("code_challenge")
if clientId == "" || responseType != "code" || redirectUri == "" { if clientId == "" || responseType != "code" || redirectUri == "" {
return "", nil return "", nil
} }

View File

@ -41,7 +41,7 @@ class ProductEditPage extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getProduct(); this.getProduct();
this.getOrganizations(); this.getOrganizations();
this.getPaymentProviders(); this.getPaymentProviders(this.state.organizationName);
} }
getProduct() { getProduct() {
@ -67,8 +67,8 @@ class ProductEditPage extends React.Component {
}); });
} }
getPaymentProviders() { getPaymentProviders(organizationName) {
ProviderBackend.getProviders(this.props.account.owner) ProviderBackend.getProviders(organizationName)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({ this.setState({

View File

@ -209,6 +209,8 @@ class ProviderEditPage extends React.Component {
return Setting.getLabel(i18next.t("provider:Public key"), i18next.t("provider:Public key - Tooltip")); return Setting.getLabel(i18next.t("provider:Public key"), i18next.t("provider:Public key - Tooltip"));
} else if (provider.type === "Msg91 SMS" || provider.type === "Infobip SMS" || provider.type === "OSON SMS") { } else if (provider.type === "Msg91 SMS" || provider.type === "Infobip SMS" || provider.type === "OSON SMS") {
return Setting.getLabel(i18next.t("provider:Sender Id"), i18next.t("provider:Sender Id - Tooltip")); return Setting.getLabel(i18next.t("provider:Sender Id"), i18next.t("provider:Sender Id - Tooltip"));
} else if (provider.type === "SendCloud SMS") {
return "SMS_USER";
} else { } else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip")); return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
} }
@ -260,6 +262,8 @@ class ProviderEditPage extends React.Component {
return Setting.getLabel(i18next.t("provider:Auth Key"), i18next.t("provider:Auth Key - Tooltip")); return Setting.getLabel(i18next.t("provider:Auth Key"), i18next.t("provider:Auth Key - Tooltip"));
} else if (provider.type === "Infobip SMS") { } else if (provider.type === "Infobip SMS") {
return Setting.getLabel(i18next.t("provider:Api Key"), i18next.t("provider:Api Key - Tooltip")); return Setting.getLabel(i18next.t("provider:Api Key"), i18next.t("provider:Api Key - Tooltip"));
} else if (provider.type === "SendCloud SMS") {
return "SMS_KEY";
} else { } else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip")); return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
} }
@ -828,6 +832,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment> </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 : ( 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"}} > <Row style={{marginTop: "20px"}} >
@ -1089,7 +1107,7 @@ class ProviderEditPage extends React.Component {
</React.Fragment> </React.Fragment>
) : this.state.provider.category === "SMS" ? ( ) : this.state.provider.category === "SMS" ? (
<React.Fragment> <React.Fragment>
{["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ? {["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS", "SendCloud SMS"].includes(this.state.provider.type) ?
null : null :
(<Row style={{marginTop: "20px"}} > (<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>

View File

@ -151,6 +151,14 @@ class RecordListPage extends BaseListPage {
sorter: true, sorter: true,
...this.getColumnSearchProps("language"), ...this.getColumnSearchProps("language"),
}, },
{
title: i18next.t("record:Status code"),
dataIndex: "statusCode",
key: "statusCode",
width: "90px",
sorter: true,
...this.getColumnSearchProps("statusCode"),
},
{ {
title: i18next.t("record:Response"), title: i18next.t("record:Response"),
dataIndex: "response", dataIndex: "response",

View File

@ -139,6 +139,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_twilio.svg`, logo: `${StaticBaseUrl}/img/social_twilio.svg`,
url: "https://www.twilio.com/messaging", url: "https://www.twilio.com/messaging",
}, },
"SendCloud SMS": {
logo: `${StaticBaseUrl}/img/sms_sendcloud.png`,
url: "https://www.sendcloud.net/",
},
"SmsBao SMS": { "SmsBao SMS": {
logo: `${StaticBaseUrl}/img/social_smsbao.png`, logo: `${StaticBaseUrl}/img/social_smsbao.png`,
url: "https://www.smsbao.com/", url: "https://www.smsbao.com/",
@ -1039,6 +1043,7 @@ export function getProviderTypeOptions(category) {
{id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"}, {id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"},
{id: "UCloud SMS", name: "UCloud SMS"}, {id: "UCloud SMS", name: "UCloud SMS"},
{id: "Twilio SMS", name: "Twilio SMS"}, {id: "Twilio SMS", name: "Twilio SMS"},
{id: "SendCloud SMS", name: "SendCloud SMS"},
{id: "SmsBao SMS", name: "SmsBao SMS"}, {id: "SmsBao SMS", name: "SmsBao SMS"},
{id: "SUBMAIL SMS", name: "SUBMAIL SMS"}, {id: "SUBMAIL SMS", name: "SUBMAIL SMS"},
{id: "Msg91 SMS", name: "Msg91 SMS"}, {id: "Msg91 SMS", name: "Msg91 SMS"},

View File

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

View File

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

View File

@ -384,8 +384,7 @@ export function getAuthUrl(application, provider, method, code) {
let endpoint = authInfo[provider.type].endpoint; let endpoint = authInfo[provider.type].endpoint;
let redirectUri = `${window.location.origin}/callback`; 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 isShortState = (provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger")) || (provider.type === "Twitter");
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState); const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier")) const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
@ -396,6 +395,8 @@ export function getAuthUrl(application, provider, method, code) {
} }
} else if (provider.type === "Apple") { } else if (provider.type === "Apple") {
redirectUri = `${window.location.origin}/api/callback`; 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 === "QQ" || provider.type === "Facebook"

View File

@ -389,6 +389,14 @@ class SignupPage extends React.Component {
return Promise.reject(i18next.t("signup:The input is not valid Email!")); 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}); this.setState({validEmail: true});
return Promise.resolve(); return Promise.resolve();
}, },

View File

@ -13,11 +13,14 @@
// limitations under the License. // limitations under the License.
import {Select} from "antd"; import {Select} from "antd";
import i18next from "i18next";
import * as Setting from "../../Setting"; import * as Setting from "../../Setting";
import React from "react"; import React from "react";
const {Option} = Select;
export const CountryCodeSelect = (props) => { export const CountryCodeSelect = (props) => {
const {onChange, style, disabled, initValue} = props; const {onChange, style, disabled, initValue, mode} = props;
const countryCodes = props.countryCodes ?? []; const countryCodes = props.countryCodes ?? [];
const [value, setValue] = React.useState(""); const [value, setValue] = React.useState("");
@ -42,11 +45,19 @@ export const CountryCodeSelect = (props) => {
style={style} style={style}
disabled={disabled} disabled={disabled}
value={value} value={value}
mode={mode}
dropdownMatchSelectWidth={false} dropdownMatchSelectWidth={false}
optionLabelProp={"label"} optionLabelProp={"label"}
onChange={handleOnChange} onChange={handleOnChange}
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())} filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
> >
{
props.hasDefault ? (<Option key={"All"} value={"All"} label={i18next.t("organization:All")} text={"organization:All"} >
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
{i18next.t("organization:All")}
</div>
</Option>) : null
}
{ {
Setting.getCountryCodeData(countryCodes).map((country) => Setting.getCountryCodeOption(country)) Setting.getCountryCodeData(countryCodes).map((country) => Setting.getCountryCodeOption(country))
} }

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "Host",
"Host - Tooltip": "Name des Hosts", "Host - Tooltip": "Name des Hosts",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Kopiere den Link", "Copy Link": "Kopiere den Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 Tax ID!": "Die Eingabe ist keine Rechnungssteuer-ID!",
"The input is not invoice title!": "Der Eingabewert ist nicht die Rechnungsbezeichnung!", "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!", "The input is not valid Email!": "Die Eingabe ist keine gültige E-Mail-Adresse!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Drittanbieter-Anmeldungen, die mit dem Benutzer verknüpft sind", "3rd-party logins - Tooltip": "Drittanbieter-Anmeldungen, die mit dem Benutzer verknüpft sind",
"Address": "Adresse", "Address": "Adresse",
"Address - Tooltip": "Wohnadresse", "Address - Tooltip": "Wohnadresse",
"Address line": "Address line",
"Affiliation": "Zugehörigkeit", "Affiliation": "Zugehörigkeit",
"Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname", "Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "Eingabe des Passworts" "input password": "Eingabe des Passworts"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "Anfitrión",
"Host - Tooltip": "Nombre del anfitrión", "Host - Tooltip": "Nombre del anfitrión",
"IdP": "IdP = Proveedor de Identidad", "IdP": "IdP = Proveedor de Identidad",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copiar enlace", "Copy Link": "Copiar enlace",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 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!", "The input is not valid Email!": "¡La entrada no es un correo electrónico válido!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Accesos sociales ligados por el usuario", "3rd-party logins - Tooltip": "Accesos sociales ligados por el usuario",
"Address": "Dirección", "Address": "Dirección",
"Address - Tooltip": "Dirección residencial", "Address - Tooltip": "Dirección residencial",
"Address line": "Address line",
"Affiliation": "Afiliación", "Affiliation": "Afiliación",
"Affiliation - Tooltip": "Empleador, como el nombre de una empresa u organización", "Affiliation - Tooltip": "Empleador, como el nombre de una empresa u organización",
"Bio": "Bio - Biografía", "Bio": "Bio - Biografía",
@ -1171,6 +1176,7 @@
"input password": "Ingresar contraseña" "input password": "Ingresar contraseña"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

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 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": "Nom de l'expéditeur",
"From name - Tooltip": "Le nom affiché comme expéditeur dans les e-mails envoyés", "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": "Hôte",
"Host - Tooltip": "Nom d'hôte", "Host - Tooltip": "Nom d'hôte",
"IdP": "IdP (Identité Fournisseur)", "IdP": "IdP (Identité Fournisseur)",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copier le lien", "Copy Link": "Copier le lien",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 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 !", "The input is not valid Email!": "L'entrée n'est pas une adresse e-mail valide !",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Service de connexions tiers liés au compte", "3rd-party logins - Tooltip": "Service de connexions tiers liés au compte",
"Address": "Adresse", "Address": "Adresse",
"Address - Tooltip": "Adresse résidentielle", "Address - Tooltip": "Adresse résidentielle",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employeur, tel que le nom de l'entreprise ou de l'organisation", "Affiliation - Tooltip": "Employeur, tel que le nom de l'entreprise ou de l'organisation",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "saisir le mot de passe" "input password": "saisir le mot de passe"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "Tuan rumah",
"Host - Tooltip": "Nama tuan rumah", "Host - Tooltip": "Nama tuan rumah",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Salin Tautan", "Copy Link": "Salin Tautan",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 Tax ID!": "Input ini bukan Tax ID faktur!",
"The input is not invoice title!": "Masukan bukan judul 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!", "The input is not valid Email!": "Input yang dimasukkan bukan sesuai dengan format Email yang valid!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Masuk sosial yang terhubung oleh pengguna", "3rd-party logins - Tooltip": "Masuk sosial yang terhubung oleh pengguna",
"Address": "Alamat", "Address": "Alamat",
"Address - Tooltip": "Alamat tempat tinggal", "Address - Tooltip": "Alamat tempat tinggal",
"Address line": "Address line",
"Affiliation": "Afiliasi", "Affiliation": "Afiliasi",
"Affiliation - Tooltip": "Pemberi Kerja, seperti nama perusahaan atau nama organisasi", "Affiliation - Tooltip": "Pemberi Kerja, seperti nama perusahaan atau nama organisasi",
"Bio": "Bio: Biografi", "Bio": "Bio: Biografi",
@ -1171,6 +1176,7 @@
"input password": "masukkan kata sandi" "input password": "masukkan kata sandi"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "ホストの名前", "Host - Tooltip": "ホストの名前",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "コピー リンク", "Copy Link": "コピー リンク",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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!": "入力されたものは有効なメールではありません",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "ユーザーによってリンクされたソーシャルログイン", "3rd-party logins - Tooltip": "ユーザーによってリンクされたソーシャルログイン",
"Address": "住所", "Address": "住所",
"Address - Tooltip": "住所", "Address - Tooltip": "住所",
"Address line": "Address line",
"Affiliation": "所属", "Affiliation": "所属",
"Affiliation - Tooltip": "企業名や団体名などの雇用主", "Affiliation - Tooltip": "企業名や団体名などの雇用主",
"Bio": "バイオ技術", "Bio": "バイオ技術",
@ -1171,6 +1176,7 @@
"input password": "パスワードを入力してください" "input password": "パスワードを入力してください"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "호스트의 이름", "Host - Tooltip": "호스트의 이름",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "링크 복사하기", "Copy Link": "링크 복사하기",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 Tax ID!": "입력한 것은 송장 세금 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!": "입력 값은 유효한 이메일이 아닙니다!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "사용자가 연결한 소셜 로그인", "3rd-party logins - Tooltip": "사용자가 연결한 소셜 로그인",
"Address": "주소", "Address": "주소",
"Address - Tooltip": "주거지 주소", "Address - Tooltip": "주거지 주소",
"Address line": "Address line",
"Affiliation": "소속", "Affiliation": "소속",
"Affiliation - Tooltip": "고용주, 회사명 또는 조직명", "Affiliation - Tooltip": "고용주, 회사명 또는 조직명",
"Bio": "바이오", "Bio": "바이오",
@ -1171,6 +1176,7 @@
"input password": "비밀번호를 입력해주세요" "input password": "비밀번호를 입력해주세요"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Endereço de e-mail do remetente", "From address - Tooltip": "Endereço de e-mail do remetente",
"From name": "Nome do remetente", "From name": "Nome do remetente",
"From name - Tooltip": "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": "Host",
"Host - Tooltip": "Nome do host", "Host - Tooltip": "Nome do host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copiar Link", "Copy Link": "Copiar Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 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!", "The input is not valid Email!": "A entrada não é um Email válido!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Logins sociais vinculados pelo usuário", "3rd-party logins - Tooltip": "Logins sociais vinculados pelo usuário",
"Address": "Endereço", "Address": "Endereço",
"Address - Tooltip": "Endereço residencial", "Address - Tooltip": "Endereço residencial",
"Address line": "Address line",
"Affiliation": "Afiliação", "Affiliation": "Afiliação",
"Affiliation - Tooltip": "Empregador, como nome da empresa ou organização", "Affiliation - Tooltip": "Empregador, como nome da empresa ou organização",
"Bio": "Biografia", "Bio": "Biografia",
@ -1171,6 +1176,7 @@
"input password": "Digite a senha" "input password": "Digite a senha"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "Имя хоста", "Host - Tooltip": "Имя хоста",
"IdP": "ИдП", "IdP": "ИдП",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Копировать ссылку", "Copy Link": "Копировать ссылку",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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!": "Ввод не является действительным адресом электронной почты!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Социальные логины, связанные пользователем", "3rd-party logins - Tooltip": "Социальные логины, связанные пользователем",
"Address": "Адрес", "Address": "Адрес",
"Address - Tooltip": "Адрес проживания", "Address - Tooltip": "Адрес проживания",
"Address line": "Address line",
"Affiliation": "Принадлежность", "Affiliation": "Принадлежность",
"Affiliation - Tooltip": "Работодатель, такой как название компании или организации", "Affiliation - Tooltip": "Работодатель, такой как название компании или организации",
"Bio": "Био", "Bio": "Био",
@ -1171,6 +1176,7 @@
"input password": "введите пароль" "input password": "введите пароль"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "input password" "input password": "input password"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "Email address of \"From\"", "From address - Tooltip": "Email address of \"From\"",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "Name of \"From\"", "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": "Host",
"Host - Tooltip": "Name of host", "Host - Tooltip": "Name of host",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Copy Link", "Copy Link": "Copy Link",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Social logins linked by the user", "3rd-party logins - Tooltip": "Social logins linked by the user",
"Address": "Address", "Address": "Address",
"Address - Tooltip": "Residential address", "Address - Tooltip": "Residential address",
"Address line": "Address line",
"Affiliation": "Affiliation", "Affiliation": "Affiliation",
"Affiliation - Tooltip": "Employer, such as company name or organization name", "Affiliation - Tooltip": "Employer, such as company name or organization name",
"Bio": "Bio", "Bio": "Bio",
@ -1171,6 +1176,7 @@
"input password": "şifreyi girin" "input password": "şifreyi girin"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

View File

@ -80,6 +80,7 @@
"Only signup": "Тільки реєстрація", "Only signup": "Тільки реєстрація",
"Org choice mode": "Режим вибору організації", "Org choice mode": "Режим вибору організації",
"Org choice mode - Tooltip": "Режим вибору організації підказка", "Org choice mode - Tooltip": "Режим вибору організації підказка",
"Please enable \\\"Signin session\\\" first before enabling \\\"Auto signin\\\"": "Please enable \\\"Signin session\\\" first before enabling \\\"Auto signin\\\"",
"Please input your application!": "Будь ласка, введіть свою заявку!", "Please input your application!": "Будь ласка, введіть свою заявку!",
"Please input your organization!": "Будь ласка, введіть вашу організацію!", "Please input your organization!": "Будь ласка, введіть вашу організацію!",
"Please select a HTML file": "Виберіть файл HTML", "Please select a HTML file": "Виберіть файл HTML",
@ -229,6 +230,7 @@
"Email": "Електронна пошта", "Email": "Електронна пошта",
"Email - Tooltip": "Дійсна електронна пошта", "Email - Tooltip": "Дійсна електронна пошта",
"Email only": "Лише електронна пошта", "Email only": "Лише електронна пошта",
"Email or Phone": "Email or Phone",
"Enable": "Увімкнути", "Enable": "Увімкнути",
"Enable dark logo": "Увімкнути темний логотип", "Enable dark logo": "Увімкнути темний логотип",
"Enable dark logo - Tooltip": "Увімкнути темний логотип", "Enable dark logo - Tooltip": "Увімкнути темний логотип",
@ -311,6 +313,7 @@
"Phone": "Телефон", "Phone": "Телефон",
"Phone - Tooltip": "Номер телефону", "Phone - Tooltip": "Номер телефону",
"Phone only": "Тільки телефон", "Phone only": "Тільки телефон",
"Phone or Email": "Phone or Email",
"Plan": "План", "Plan": "План",
"Plan - Tooltip": "План підказка", "Plan - Tooltip": "План підказка",
"Plans": "Плани", "Plans": "Плани",
@ -391,6 +394,7 @@
"User type": "Тип користувача", "User type": "Тип користувача",
"User type - Tooltip": "Теги, до яких належить користувач, за умовчанням \"звичайний користувач\"", "User type - Tooltip": "Теги, до яких належить користувач, за умовчанням \"звичайний користувач\"",
"Users": "Користувачі", "Users": "Користувачі",
"Users - Tooltip": "Users - Tooltip",
"Users under all organizations": "Користувачі в усіх організаціях", "Users under all organizations": "Користувачі в усіх організаціях",
"Verifications": "Перевірки", "Verifications": "Перевірки",
"Webhooks": "Веб-хуки", "Webhooks": "Веб-хуки",
@ -473,7 +477,6 @@
"LDAP username, Email or phone": "Ім’я користувача LDAP, електронна пошта або телефон", "LDAP username, Email or phone": "Ім’я користувача LDAP, електронна пошта або телефон",
"Loading": "Завантаження", "Loading": "Завантаження",
"Logging out...": "Вихід...", "Logging out...": "Вихід...",
"Login button": "Кнопка входу",
"MetaMask plugin not detected": "Плагін MetaMask не виявлено", "MetaMask plugin not detected": "Плагін MetaMask не виявлено",
"Model loading failure": "Помилка завантаження моделі", "Model loading failure": "Помилка завантаження моделі",
"No account?": "Немає облікового запису?", "No account?": "Немає облікового запису?",
@ -498,6 +501,7 @@
"Sign in with Face ID": "Увійдіть за допомогою Face ID", "Sign in with Face ID": "Увійдіть за допомогою Face ID",
"Sign in with WebAuthn": "Увійдіть за допомогою WebAuthn", "Sign in with WebAuthn": "Увійдіть за допомогою WebAuthn",
"Sign in with {type}": "Увійдіть за допомогою {type}", "Sign in with {type}": "Увійдіть за допомогою {type}",
"Signin button": "Signin button",
"Signing in...": "Вхід...", "Signing in...": "Вхід...",
"Successfully logged in with WebAuthn credentials": "Успішно ввійшли за допомогою облікових даних WebAuthn", "Successfully logged in with WebAuthn credentials": "Успішно ввійшли за допомогою облікових даних WebAuthn",
"The camera is currently in use by another webpage": "Камера зараз використовується іншою веб-сторінкою", "The camera is currently in use by another webpage": "Камера зараз використовується іншою веб-сторінкою",
@ -774,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": "Ім'я хоста", "Host - Tooltip": "Ім'я хоста",
"IdP": "IDP", "IdP": "IDP",
@ -893,7 +899,8 @@
"record": { "record": {
"Is triggered": "Спрацьовує", "Is triggered": "Спрацьовує",
"Object": "Об'єкт", "Object": "Об'єкт",
"Response": "Відповідь" "Response": "Відповідь",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Копіювати посилання", "Copy Link": "Копіювати посилання",
@ -941,6 +948,7 @@
"Please select your country code!": "Виберіть код країни!", "Please select your country code!": "Виберіть код країни!",
"Please select your country/region!": "Виберіть свою країну/регіон!", "Please select your country/region!": "Виберіть свою країну/регіон!",
"Regex": "Регулярний вираз", "Regex": "Регулярний вираз",
"Signup button": "Signup button",
"Terms of Use": "Умови використання", "Terms of Use": "Умови використання",
"Terms of Use - Tooltip": "Умови використання, з якими користувачі повинні ознайомитися та погодитися під час реєстрації", "Terms of Use - Tooltip": "Умови використання, з якими користувачі повинні ознайомитися та погодитися під час реєстрації",
"Text 1": "Текст 1", "Text 1": "Текст 1",
@ -948,6 +956,7 @@
"Text 3": "Текст 3", "Text 3": "Текст 3",
"Text 4": "Текст 4", "Text 4": "Текст 4",
"Text 5": "Текст 5", "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 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!": "Введена недійсна адреса електронної пошти!",
@ -1068,6 +1077,7 @@
"3rd-party logins - Tooltip": "Соціальні входи, пов’язані користувачем", "3rd-party logins - Tooltip": "Соціальні входи, пов’язані користувачем",
"Address": "Адреса", "Address": "Адреса",
"Address - Tooltip": "Адреса місця проживання", "Address - Tooltip": "Адреса місця проживання",
"Address line": "Address line",
"Affiliation": "Приналежність", "Affiliation": "Приналежність",
"Affiliation - Tooltip": "Роботодавець, наприклад назва компанії чи організації", "Affiliation - Tooltip": "Роботодавець, наприклад назва компанії чи організації",
"Bio": "біографія", "Bio": "біографія",
@ -1162,9 +1172,11 @@
"Values": "Цінності", "Values": "Цінності",
"Verification code sent": "Код підтвердження надіслано", "Verification code sent": "Код підтвердження надіслано",
"WebAuthn credentials": "Облікові дані WebAuthn", "WebAuthn credentials": "Облікові дані WebAuthn",
"You have changed the username, please save your change first before modifying the password": "You have changed the username, please save your change first before modifying the password",
"input password": "введіть пароль" "input password": "введіть пароль"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Приймач" "Receiver": "Приймач"
}, },
"webhook": { "webhook": {

View File

@ -778,6 +778,8 @@
"From address - Tooltip": "From address - Tooltip", "From address - Tooltip": "From address - Tooltip",
"From name": "From name", "From name": "From name",
"From name - Tooltip": "From name - Tooltip", "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": "Chủ nhà",
"Host - Tooltip": "Tên của người chủ chỗ ở", "Host - Tooltip": "Tên của người chủ chỗ ở",
"IdP": "IdP", "IdP": "IdP",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "Is triggered", "Is triggered": "Is triggered",
"Object": "Object", "Object": "Object",
"Response": "Response" "Response": "Response",
"Status code": "Status code"
}, },
"resource": { "resource": {
"Copy Link": "Sao chép liên kết", "Copy Link": "Sao chép liên kết",
@ -953,6 +956,7 @@
"Text 3": "Text 3", "Text 3": "Text 3",
"Text 4": "Text 4", "Text 4": "Text 4",
"Text 5": "Text 5", "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 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 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ệ!", "The input is not valid Email!": "Đầu vào không phải là địa chỉ Email hợp lệ!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "Đăng nhập xã hội liên kết bởi người dùng", "3rd-party logins - Tooltip": "Đăng nhập xã hội liên kết bởi người dùng",
"Address": "Địa chỉ", "Address": "Địa chỉ",
"Address - Tooltip": "Địa chỉ cư trú", "Address - Tooltip": "Địa chỉ cư trú",
"Address line": "Address line",
"Affiliation": "Liên kết", "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", "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", "Bio": "bản vẻ đời sống",
@ -1171,6 +1176,7 @@
"input password": "Nhập mật khẩu" "input password": "Nhập mật khẩu"
}, },
"verification": { "verification": {
"Is used": "Is used",
"Receiver": "Receiver" "Receiver": "Receiver"
}, },
"webhook": { "webhook": {

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 - Tooltip": "如果启用获取手机号码你需要先启用peopleApi并添加范围https://www.googleapis.com/auth/user.phonenumbers.read",
"Host": "主机", "Host": "主机",
"Host - Tooltip": "主机名", "Host - Tooltip": "主机名",
"IdP": "身份提供商", "IdP": "身份提供商",
@ -897,7 +899,8 @@
"record": { "record": {
"Is triggered": "是否触发", "Is triggered": "是否触发",
"Object": "实体", "Object": "实体",
"Response": "响应" "Response": "响应",
"Status code": "状态码"
}, },
"resource": { "resource": {
"Copy Link": "复制链接", "Copy Link": "复制链接",
@ -953,6 +956,7 @@
"Text 3": "文本3", "Text 3": "文本3",
"Text 4": "文本4", "Text 4": "文本4",
"Text 5": "文本5", "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 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!": "您输入的电子邮箱格式有误!",
@ -1073,6 +1077,7 @@
"3rd-party logins - Tooltip": "用户所绑定的社会化登录", "3rd-party logins - Tooltip": "用户所绑定的社会化登录",
"Address": "地址", "Address": "地址",
"Address - Tooltip": "居住地址", "Address - Tooltip": "居住地址",
"Address line": "地址",
"Affiliation": "工作单位", "Affiliation": "工作单位",
"Affiliation - Tooltip": "工作单位,如公司、组织名称", "Affiliation - Tooltip": "工作单位,如公司、组织名称",
"Bio": "自我介绍", "Bio": "自我介绍",
@ -1171,6 +1176,7 @@
"input password": "输入密码" "input password": "输入密码"
}, },
"verification": { "verification": {
"Is used": "已使用",
"Receiver": "接收者" "Receiver": "接收者"
}, },
"webhook": { "webhook": {

View File

@ -15,6 +15,7 @@
import React from "react"; import React from "react";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd"; import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import i18next from "i18next"; import i18next from "i18next";
import * as Provider from "../auth/Provider"; import * as Provider from "../auth/Provider";
@ -29,6 +30,10 @@ class ProviderTable extends React.Component {
}; };
} }
getUserOrganization() {
return this.props.application?.organizationObj;
}
updateTable(table) { updateTable(table) {
this.props.onUpdateTable(table); this.props.onUpdateTable(table);
} }
@ -79,7 +84,7 @@ class ProviderTable extends React.Component {
// If the provider is email or SMS, set the rule to "all" instead of the default "None" // If the provider is email or SMS, set the rule to "all" instead of the default "None"
if (provider.category === "Email" || provider.category === "SMS") { if (provider.category === "Email" || provider.category === "SMS") {
this.updateField(table, index, "rule", "all"); this.updateField(table, index, "rule", "All");
} }
}} > }} >
{ {
@ -109,6 +114,30 @@ class ProviderTable extends React.Component {
return Provider.getProviderLogoWidget(provider); return Provider.getProviderLogoWidget(provider);
}, },
}, },
{
title: i18next.t("user:Country/Region"),
dataIndex: "countryCodes",
key: "countryCodes",
width: "140px",
render: (text, record, index) => {
if (record.provider?.category !== "SMS") {
return null;
}
return (
<CountryCodeSelect
style={{width: "100%"}}
hasDefault={true}
mode={"multiple"}
initValue={text ? text : ["All"]}
onChange={(value) => {
this.updateField(table, index, "countryCodes", value);
}}
countryCodes={this.getUserOrganization()?.countryCodes}
/>
);
},
},
{ {
title: i18next.t("provider:Can signup"), title: i18next.t("provider:Can signup"),
dataIndex: "canSignUp", dataIndex: "canSignUp",
@ -198,7 +227,7 @@ class ProviderTable extends React.Component {
title: i18next.t("application:Rule"), title: i18next.t("application:Rule"),
dataIndex: "rule", dataIndex: "rule",
key: "rule", key: "rule",
width: "120px", width: "160px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.provider?.type === "Google") { if (record.provider?.type === "Google") {
if (text === "None") { if (text === "None") {
@ -230,12 +259,12 @@ class ProviderTable extends React.Component {
); );
} else if (record.provider?.category === "SMS" || record.provider?.category === "Email") { } else if (record.provider?.category === "SMS" || record.provider?.category === "Email") {
if (text === "None") { if (text === "None") {
text = "all"; text = "All";
} }
return ( return (
<Select virtual={false} style={{width: "100%"}} <Select virtual={false} style={{width: "100%"}}
value={text} value={text}
defaultValue="all" defaultValue="All"
onChange={value => { onChange={value => {
this.updateField(table, index, "rule", value); this.updateField(table, index, "rule", value);
}}> }}>

View File

@ -134,7 +134,7 @@ class SigninTable extends React.Component {
value={getItemDisplayName(text)} value={getItemDisplayName(text)}
onChange={value => { onChange={value => {
this.updateField(table, index, "name", 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>) 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", dataIndex: "label",
key: "label", key: "label",
width: "200px", width: "200px",
@ -188,14 +188,18 @@ class SigninTable extends React.Component {
}} /> }} />
</Popover> </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; return null;
}, },
}, },
{ {
title: i18next.t("application:Custom CSS"), title: i18next.t("application:Custom CSS"),
dataIndex: "label", dataIndex: "customCss",
key: "label", key: "customCss",
width: "200px", width: "200px",
render: (text, record, index) => { render: (text, record, index) => {
if (!record.name.startsWith("Text ") && !record?.isCustom) { if (!record.name.startsWith("Text ") && !record?.isCustom) {
@ -205,13 +209,13 @@ class SigninTable extends React.Component {
<CodeMirror value={text?.replaceAll("<style>", "").replaceAll("</style>", "")} <CodeMirror value={text?.replaceAll("<style>", "").replaceAll("</style>", "")}
options={{mode: "css", theme: "material-darker"}} options={{mode: "css", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => { onBeforeChange={(editor, data, value) => {
this.updateField(table, index, "label", value); this.updateField(table, index, "customCss", value);
}} }}
/> />
</div> </div>
} title={i18next.t("application:CSS style")} trigger="click"> } title={i18next.t("application:CSS style")} trigger="click">
<Input value={text?.replaceAll("<style>", "").replaceAll("</style>", "")} onChange={e => { <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> </Popover>
); );