mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-01 18:00:31 +08:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8f6c295c40 | ||
![]() |
2f31e35315 | ||
![]() |
b6d6aa9d04 | ||
![]() |
f40d44fa1c | ||
![]() |
3b2820cbe3 | ||
![]() |
764e88f603 | ||
![]() |
7f298efebc | ||
![]() |
0fc48bb6cd | ||
![]() |
c3b3840994 | ||
![]() |
eacc3fae5a | ||
![]() |
ce7a2e924b | ||
![]() |
ece060d03d | ||
![]() |
1276da4daa | ||
![]() |
616629ef99 | ||
![]() |
b633ecdcf2 | ||
![]() |
a12ba7fb85 | ||
![]() |
08a0092974 | ||
![]() |
bb04b10e8b | ||
![]() |
ea1414dfd0 | ||
![]() |
32a8a028d5 | ||
![]() |
0fe34c2f53 | ||
![]() |
dc57c476b7 | ||
![]() |
a7cb202ee9 | ||
![]() |
e5e264628e | ||
![]() |
8d4127f744 | ||
![]() |
1305899060 | ||
![]() |
411a85c7ab | ||
![]() |
f39358e122 | ||
![]() |
a84752bbb5 | ||
![]() |
e9d8ab8cdb | ||
![]() |
d12088e8e7 | ||
![]() |
c62588f9bc | ||
![]() |
16cd09d175 | ||
![]() |
7318ee6e3a | ||
![]() |
3459ef1479 | ||
![]() |
ca6b27f922 | ||
![]() |
e528e8883b | ||
![]() |
b7cd604e56 | ||
![]() |
3c2fd574a6 | ||
![]() |
a9de7d3aef | ||
![]() |
9820801634 | ||
![]() |
c6e422c3a8 | ||
![]() |
bc8e9cfd64 | ||
![]() |
c1eae9fcd8 | ||
![]() |
6dae6e4954 | ||
![]() |
559a91e8ee | ||
![]() |
b0aaf09ef1 | ||
![]() |
7e2f67c49a | ||
![]() |
e584a6a111 |
@@ -64,7 +64,6 @@ 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/version_info.txt ./go/src/casdoor/version_info.txt
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
RUN mkdir tempFiles
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
@@ -46,6 +46,7 @@ p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, POST, /api/callback, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
@@ -88,6 +89,7 @@ p, *, *, *, /api/metrics, *, *
|
||||
p, *, *, GET, /api/get-pricing, *, *
|
||||
p, *, *, GET, /api/get-plan, *, *
|
||||
p, *, *, GET, /api/get-subscription, *, *
|
||||
p, *, *, GET, /api/get-provider, *, *
|
||||
p, *, *, GET, /api/get-organization-names, *, *
|
||||
`
|
||||
|
||||
@@ -120,6 +122,10 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if subOwner == "app" {
|
||||
return true
|
||||
}
|
||||
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
@@ -119,20 +118,10 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
}
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser, err := object.GetLastUser(authForm.Organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
lastIdInt := -1
|
||||
if lastUser != nil {
|
||||
lastIdInt = util.ParseInt(lastUser.Id)
|
||||
}
|
||||
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
id, err := object.GenerateIdForNewUser(application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
username := authForm.Username
|
||||
@@ -309,27 +298,32 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
if user == "" {
|
||||
user = util.GetId(token.Organization, token.User)
|
||||
}
|
||||
if user == "" {
|
||||
user = util.GetId(token.Organization, token.User)
|
||||
}
|
||||
|
||||
c.ClearUserSession()
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
owner, username := util.GetOwnerAndNameFromId(user)
|
||||
c.ClearUserSession()
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
owner, username := util.GetOwnerAndNameFromId(user)
|
||||
|
||||
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
_, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
if redirectUri == "" {
|
||||
c.ResponseOk()
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -90,14 +90,24 @@ func (c *ApiController) GetApplication() {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Input().Get("withKey") != "" && application.Cert != "" {
|
||||
if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
|
||||
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
application.CertPublicKey = cert.Certificate
|
||||
if cert == nil {
|
||||
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
application.CertPublicKey = cert.Certificate
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -59,7 +60,7 @@ func tokenToResponse(token *object.Token) *Response {
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
|
||||
allowed, err := object.CheckAccessPermission(userId, application)
|
||||
allowed, err := object.CheckLoginPermission(userId, application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
@@ -896,3 +897,16 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
}
|
||||
|
||||
// Callback
|
||||
// @Title Callback
|
||||
// @Tag Callback API
|
||||
// @Description Get Login Error Counts
|
||||
// @router /api/Callback [post]
|
||||
func (c *ApiController) Callback() {
|
||||
code := c.GetString("code")
|
||||
state := c.GetString("state")
|
||||
|
||||
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
||||
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
||||
}
|
||||
|
@@ -176,11 +176,10 @@ func (c *ApiController) DeletePayment() {
|
||||
func (c *ApiController) NotifyPayment() {
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
orderId := c.Ctx.Input.Param("order")
|
||||
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName, orderId)
|
||||
payment, err := object.NotifyPayment(body, owner, paymentName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -187,11 +187,11 @@ func (c *ApiController) BuyProduct() {
|
||||
return
|
||||
}
|
||||
|
||||
payUrl, orderId, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
||||
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payUrl, orderId)
|
||||
c.ResponseOk(payment)
|
||||
}
|
||||
|
@@ -156,7 +156,7 @@ func (c *ApiController) DeleteToken() {
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router /login/oauth/access_token [post]
|
||||
// @router api/login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
|
@@ -258,6 +258,13 @@ func (c *ApiController) UpdateUser() {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Input().Get("allowEmpty") == "" {
|
||||
if user.DisplayName == "" {
|
||||
c.ResponseError(c.T("user:Display name cannot be empty"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
@@ -441,12 +448,25 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
|
||||
targetUser, err := object.GetUser(userId)
|
||||
if targetUser == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if oldPassword != "" {
|
||||
isAdmin := c.IsAdmin()
|
||||
if isAdmin {
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
|
227
email/azure_acs.go
Normal file
227
email/azure_acs.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
importanceNormal = "normal"
|
||||
sendEmailEndpoint = "/emails:send"
|
||||
apiVersion = "2023-03-31"
|
||||
)
|
||||
|
||||
type Email struct {
|
||||
Recipients Recipients `json:"recipients"`
|
||||
SenderAddress string `json:"senderAddress"`
|
||||
Content Content `json:"content"`
|
||||
Headers []CustomHeader `json:"headers"`
|
||||
Tracking bool `json:"disableUserEngagementTracking"`
|
||||
Importance string `json:"importance"`
|
||||
ReplyTo []EmailAddress `json:"replyTo"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
type Recipients struct {
|
||||
To []EmailAddress `json:"to"`
|
||||
CC []EmailAddress `json:"cc"`
|
||||
BCC []EmailAddress `json:"bcc"`
|
||||
}
|
||||
|
||||
type EmailAddress struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Subject string `json:"subject"`
|
||||
HTML string `json:"html"`
|
||||
PlainText string `json:"plainText"`
|
||||
}
|
||||
|
||||
type CustomHeader struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Content string `json:"contentBytesBase64"`
|
||||
AttachmentType string `json:"attachmentType"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Error CommunicationError `json:"error"`
|
||||
}
|
||||
|
||||
// CommunicationError contains the error code and message
|
||||
type CommunicationError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AzureACSEmailProvider struct {
|
||||
AccessKey string
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
func NewAzureACSEmailProvider(accessKey string, endpoint string) *AzureACSEmailProvider {
|
||||
return &AzureACSEmailProvider{
|
||||
AccessKey: accessKey,
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func newEmail(fromAddress string, toAddress string, subject string, content string) *Email {
|
||||
return &Email{
|
||||
Recipients: Recipients{
|
||||
To: []EmailAddress{
|
||||
{
|
||||
DisplayName: toAddress,
|
||||
Address: toAddress,
|
||||
},
|
||||
},
|
||||
},
|
||||
SenderAddress: fromAddress,
|
||||
Content: Content{
|
||||
Subject: subject,
|
||||
HTML: content,
|
||||
},
|
||||
Importance: importanceNormal,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
||||
postBody, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("email JSON marshall failed: %s", err)
|
||||
}
|
||||
|
||||
bodyBuffer := bytes.NewBuffer(postBody)
|
||||
|
||||
req, err := http.NewRequest("POST", a.Endpoint+sendEmailEndpoint+"?api-version="+apiVersion, bodyBuffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating AzureACS API request: %s", err)
|
||||
}
|
||||
|
||||
// Sign the request using the AzureACS access key and HMAC-SHA256
|
||||
err = signRequestHMAC(a.AccessKey, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error signing AzureACS API request: %s", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Some important header
|
||||
req.Header.Set("repeatability-request-id", uuid.New().String())
|
||||
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
|
||||
|
||||
// Send request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending AzureACS API request: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Response error Handling
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
commError := ErrorResponse{}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&commError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("error sending email: %s", commError.Error.Message)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
return fmt.Errorf("error sending email: status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func signRequestHMAC(secret string, req *http.Request) error {
|
||||
method := req.Method
|
||||
host := req.URL.Host
|
||||
pathAndQuery := req.URL.Path
|
||||
|
||||
if req.URL.RawQuery != "" {
|
||||
pathAndQuery = pathAndQuery + "?" + req.URL.RawQuery
|
||||
}
|
||||
|
||||
var content []byte
|
||||
var err error
|
||||
if req.Body != nil {
|
||||
content, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
// return err
|
||||
content = []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(content))
|
||||
|
||||
key, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding secret: %s", err)
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format(http.TimeFormat)
|
||||
contentHash := GetContentHashBase64(content)
|
||||
stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", strings.ToUpper(method), pathAndQuery, timestamp, host, contentHash)
|
||||
signature := GetHmac(stringToSign, key)
|
||||
|
||||
req.Header.Set("x-ms-content-sha256", contentHash)
|
||||
req.Header.Set("x-ms-date", timestamp)
|
||||
|
||||
req.Header.Set("Authorization", "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature="+signature)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetContentHashBase64(content []byte) string {
|
||||
hasher := sha256.New()
|
||||
hasher.Write(content)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func GetHmac(content string, key []byte) string {
|
||||
hmac := hmac.New(sha256.New, key)
|
||||
hmac.Write([]byte(content))
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
|
||||
}
|
||||
|
||||
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||
e := newEmail(fromAddress, toAddress, subject, content)
|
||||
|
||||
return a.sendEmail(e)
|
||||
}
|
27
email/provider.go
Normal file
27
email/provider.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package email
|
||||
|
||||
type EmailProvider interface {
|
||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||
}
|
||||
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, appId string, host string, port int, disableSsl bool) EmailProvider {
|
||||
if typ == "Azure ACS" {
|
||||
return NewAzureACSEmailProvider(appId, host)
|
||||
} else {
|
||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||
}
|
||||
}
|
49
email/smtp.go
Normal file
49
email/smtp.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/casdoor/gomail/v2"
|
||||
)
|
||||
|
||||
type SmtpEmailProvider struct {
|
||||
Dialer *gomail.Dialer
|
||||
}
|
||||
|
||||
func NewSmtpEmailProvider(userName string, password string, host string, port int, typ string, disableSsl bool) *SmtpEmailProvider {
|
||||
dialer := &gomail.Dialer{}
|
||||
dialer = gomail.NewDialer(host, port, userName, password)
|
||||
if typ == "SUBMAIL" {
|
||||
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
dialer.SSL = !disableSsl
|
||||
|
||||
return &SmtpEmailProvider{Dialer: dialer}
|
||||
}
|
||||
|
||||
func (s *SmtpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||
message := gomail.NewMessage()
|
||||
|
||||
message.SetAddressHeader("From", fromAddress, fromName)
|
||||
message.SetHeader("To", toAddress)
|
||||
message.SetHeader("Subject", subject)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
message.SkipUsernameCheck = true
|
||||
return s.Dialer.DialAndSend(message)
|
||||
}
|
20
go.mod
20
go.mod
@@ -6,14 +6,14 @@ require (
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.4
|
||||
github.com/aws/aws-sdk-go v1.45.5
|
||||
github.com/beego/beego v1.12.12
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin v1.9.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casdoor/go-sms-sender v0.12.0
|
||||
github.com/casbin/casbin/v2 v2.77.2
|
||||
github.com/casdoor/go-sms-sender v0.14.0
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/notify v0.43.0
|
||||
github.com/casdoor/oss v1.3.0
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||
@@ -30,14 +30,13 @@ require (
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nikoksr/notify v0.41.0
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
@@ -55,15 +54,18 @@ require (
|
||||
github.com/stripe/stripe-go/v74 v74.29.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/xorm-io/builder v0.3.13
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.13.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
google.golang.org/api v0.138.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
modernc.org/sqlite v1.18.2
|
||||
)
|
||||
|
@@ -38,7 +38,13 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
applyToOtherLanguage("frontend", "tr", data)
|
||||
applyToOtherLanguage("frontend", "ar", data)
|
||||
applyToOtherLanguage("frontend", "he", data)
|
||||
applyToOtherLanguage("frontend", "nl", data)
|
||||
applyToOtherLanguage("frontend", "pl", data)
|
||||
applyToOtherLanguage("frontend", "fi", data)
|
||||
applyToOtherLanguage("frontend", "sv", data)
|
||||
applyToOtherLanguage("frontend", "uk", data)
|
||||
applyToOtherLanguage("frontend", "kk", data)
|
||||
applyToOtherLanguage("frontend", "fa", data)
|
||||
}
|
||||
|
||||
func TestGenerateI18nBackend(t *testing.T) {
|
||||
@@ -60,5 +66,11 @@ func TestGenerateI18nBackend(t *testing.T) {
|
||||
applyToOtherLanguage("backend", "tr", data)
|
||||
applyToOtherLanguage("backend", "ar", data)
|
||||
applyToOtherLanguage("backend", "he", data)
|
||||
applyToOtherLanguage("backend", "nl", data)
|
||||
applyToOtherLanguage("backend", "pl", data)
|
||||
applyToOtherLanguage("backend", "fi", data)
|
||||
applyToOtherLanguage("backend", "sv", data)
|
||||
applyToOtherLanguage("backend", "uk", data)
|
||||
applyToOtherLanguage("backend", "kk", data)
|
||||
applyToOtherLanguage("backend", "fa", data)
|
||||
}
|
||||
|
142
i18n/locales/fa/data.json
Normal file
142
i18n/locales/fa/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
142
i18n/locales/kk/data.json
Normal file
142
i18n/locales/kk/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
142
i18n/locales/nl/data.json
Normal file
142
i18n/locales/nl/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
142
i18n/locales/pl/data.json
Normal file
142
i18n/locales/pl/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
142
i18n/locales/sv/data.json
Normal file
142
i18n/locales/sv/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
142
i18n/locales/uk/data.json
Normal file
142
i18n/locales/uk/data.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
"general": {
|
||||
"Missing parameter": "Missing parameter",
|
||||
"Please login first": "Please login first",
|
||||
"The user: %s doesn't exist": "The user: %s doesn't exist",
|
||||
"don't support captchaProvider: ": "don't support captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Ldap server exist"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Please link first",
|
||||
"This application has no providers": "This application has no providers",
|
||||
"This application has no providers of type": "This application has no providers of type",
|
||||
"This provider can't be unlinked": "This provider can't be unlinked",
|
||||
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
|
||||
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Application %s not found"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "provider %s's category is not SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
|
||||
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Found no credentials for this user",
|
||||
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
|
||||
}
|
||||
}
|
@@ -72,13 +72,13 @@ type FacebookCheckToken struct {
|
||||
}
|
||||
|
||||
// FacebookCheckTokenData
|
||||
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
|
||||
// Get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#checktoken
|
||||
type FacebookCheckTokenData struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
|
||||
// get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#confirm
|
||||
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
|
10
idp/goth.go
10
idp/goth.go
@@ -19,6 +19,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@@ -97,6 +98,9 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &amazon.Session{},
|
||||
}
|
||||
case "Apple":
|
||||
if !strings.Contains(redirectUrl, "/api/callback") {
|
||||
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
|
||||
}
|
||||
idp = GothIdProvider{
|
||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
||||
Session: &apple.Session{},
|
||||
@@ -392,7 +396,9 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
// Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
|
||||
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
|
||||
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
|
||||
idpClient.Set(reflect.ValueOf(client))
|
||||
if idpClient.IsValid() {
|
||||
idpClient.Set(reflect.ValueOf(client))
|
||||
}
|
||||
}
|
||||
|
||||
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
@@ -468,6 +474,8 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
|
||||
if provider == "steam" {
|
||||
user.Username = user.Id
|
||||
user.Email = ""
|
||||
} else if provider == "apple" {
|
||||
user.Username = util.GetUsernameFromEmail(user.Email)
|
||||
}
|
||||
return &user
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@
|
||||
"passwordType": "plain",
|
||||
"passwordSalt": "",
|
||||
"passwordOptions": ["AtLeast6"],
|
||||
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH"],
|
||||
"countryCodes": ["US", "GB", "ES", "FR", "DE", "CN", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH", "NL", "PL", "FI", "SE", "UA", "KZ"],
|
||||
"defaultAvatar": "",
|
||||
"defaultApplication": "",
|
||||
"tags": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "fi"],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||
"masterPassword": "",
|
||||
"initScore": 2000,
|
||||
"enableSoftDeletion": false,
|
||||
|
29
notification/bark.go
Normal file
29
notification/bark.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/bark"
|
||||
)
|
||||
|
||||
func NewBarkProvider(deviceKey string) (notify.Notifier, error) {
|
||||
barkSrv := bark.New(deviceKey)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(barkSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
33
notification/dingtalk.go
Normal file
33
notification/dingtalk.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/dingding"
|
||||
)
|
||||
|
||||
func NewDingTalkProvider(token string, secret string) (notify.Notifier, error) {
|
||||
cfg := dingding.Config{
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
}
|
||||
dingtalkSrv := dingding.New(&cfg)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(dingtalkSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
37
notification/discord.go
Normal file
37
notification/discord.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/discord"
|
||||
)
|
||||
|
||||
func NewDiscordProvider(token string, channelId string) (*notify.Notify, error) {
|
||||
discordSrv := discord.New()
|
||||
|
||||
err := discordSrv.AuthenticateWithBotToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discordSrv.SetHttpClient(proxy.ProxyHttpClient)
|
||||
discordSrv.AddReceivers(channelId)
|
||||
|
||||
notifier := notify.NewWithServices(discordSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
53
notification/google_chat.go
Normal file
53
notification/google_chat.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/googlechat"
|
||||
"google.golang.org/api/chat/v1"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
func NewGoogleChatProvider(credentials string) (*notify.Notify, error) {
|
||||
withCred := option.WithCredentialsJSON([]byte(credentials))
|
||||
withSpacesScope := option.WithScopes("https://www.googleapis.com/auth/chat.spaces")
|
||||
|
||||
listSvc, err := chat.NewService(context.Background(), withCred, withSpacesScope)
|
||||
spaces, err := listSvc.Spaces.List().Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
receivers := make([]string, 0)
|
||||
for _, space := range spaces.Spaces {
|
||||
name := strings.Replace(space.Name, "spaces/", "", 1)
|
||||
receivers = append(receivers, name)
|
||||
}
|
||||
|
||||
googleChatSrv, err := googlechat.New(withCred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
googleChatSrv.AddReceivers(receivers...)
|
||||
|
||||
notifier := notify.NewWithServices(googleChatSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
29
notification/lark.go
Normal file
29
notification/lark.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/lark"
|
||||
)
|
||||
|
||||
func NewLarkProvider(webhookURL string) (notify.Notifier, error) {
|
||||
larkSrv := lark.NewWebhookService(webhookURL)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(larkSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
32
notification/line.go
Normal file
32
notification/line.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/line"
|
||||
)
|
||||
|
||||
func NewLineProvider(channelSecret string, accessToken string, receiver string) (*notify.Notify, error) {
|
||||
lineSrv, _ := line.NewWithHttpClient(channelSecret, accessToken, proxy.ProxyHttpClient)
|
||||
|
||||
lineSrv.AddReceivers(receiver)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(lineSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
36
notification/matrix.go
Normal file
36
notification/matrix.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/matrix"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
func NewMatrixProvider(userId string, roomId string, accessToken string, homeServer string) (*notify.Notify, error) {
|
||||
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matrixSrv.SetHttpClient(proxy.ProxyHttpClient)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(matrixSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
31
notification/microsoft_teams.go
Normal file
31
notification/microsoft_teams.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/msteams"
|
||||
)
|
||||
|
||||
func NewMicrosoftTeamsProvider(webhookURL string) (notify.Notifier, error) {
|
||||
msTeamsSrv := msteams.New()
|
||||
|
||||
msTeamsSrv.AddReceivers(webhookURL)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(msTeamsSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
@@ -14,13 +14,45 @@
|
||||
|
||||
package notification
|
||||
|
||||
import "github.com/nikoksr/notify"
|
||||
import "github.com/casdoor/notify"
|
||||
|
||||
func GetNotificationProvider(typ string, appId string, receiver string, method string, title string) (notify.Notifier, error) {
|
||||
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
||||
if typ == "Telegram" {
|
||||
return NewTelegramProvider(appId, receiver)
|
||||
} else if typ == "Custom HTTP" {
|
||||
return NewCustomHttpProvider(receiver, method, title)
|
||||
} else if typ == "DingTalk" {
|
||||
return NewDingTalkProvider(appId, receiver)
|
||||
} else if typ == "Lark" {
|
||||
return NewLarkProvider(receiver)
|
||||
} else if typ == "Microsoft Teams" {
|
||||
return NewMicrosoftTeamsProvider(receiver)
|
||||
} else if typ == "Bark" {
|
||||
return NewBarkProvider(receiver)
|
||||
} else if typ == "Pushover" {
|
||||
return NewPushoverProvider(appId, receiver)
|
||||
} else if typ == "Pushbullet" {
|
||||
return NewPushbulletProvider(appId, receiver)
|
||||
} else if typ == "Slack" {
|
||||
return NewSlackProvider(appId, receiver)
|
||||
} else if typ == "Webpush" {
|
||||
return NewWebpushProvider(clientId, clientSecret, receiver)
|
||||
} else if typ == "Discord" {
|
||||
return NewDiscordProvider(appId, receiver)
|
||||
} else if typ == "Google Chat" {
|
||||
return NewGoogleChatProvider(metaData)
|
||||
} else if typ == "Line" {
|
||||
return NewLineProvider(clientSecret, appId, receiver)
|
||||
} else if typ == "Matrix" {
|
||||
return NewMatrixProvider(clientId, clientSecret, appId, receiver)
|
||||
} else if typ == "Twitter" {
|
||||
return NewTwitterProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
|
||||
} else if typ == "Reddit" {
|
||||
return NewRedditProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
|
||||
} else if typ == "Rocket Chat" {
|
||||
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
|
||||
} else if typ == "Viber" {
|
||||
return NewViberProvider(clientId, clientSecret, appId, receiver)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
31
notification/pushbullet.go
Normal file
31
notification/pushbullet.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/pushbullet"
|
||||
)
|
||||
|
||||
func NewPushbulletProvider(apiToken string, deviceNickname string) (notify.Notifier, error) {
|
||||
pushbulletSrv := pushbullet.New(apiToken)
|
||||
|
||||
pushbulletSrv.AddReceivers(deviceNickname)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(pushbulletSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
31
notification/pushover.go
Normal file
31
notification/pushover.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/pushover"
|
||||
)
|
||||
|
||||
func NewPushoverProvider(appToken string, recipientID string) (notify.Notifier, error) {
|
||||
pushoverSrv := pushover.New(appToken)
|
||||
|
||||
pushoverSrv.AddReceivers(recipientID)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(pushoverSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
34
notification/reddit.go
Normal file
34
notification/reddit.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/reddit"
|
||||
)
|
||||
|
||||
func NewRedditProvider(clientId string, clientSecret string, username string, password string, recipient string) (notify.Notifier, error) {
|
||||
redditSrv, err := reddit.New(clientId, clientSecret, username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
redditSrv.AddReceivers(recipient)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(redditSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
47
notification/rocket_chat.go
Normal file
47
notification/rocket_chat.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/rocketchat"
|
||||
)
|
||||
|
||||
func NewRocketChatProvider(clientId string, clientSecret string, endpoint string, channelName string) (notify.Notifier, error) {
|
||||
parts := strings.Split(endpoint, "://")
|
||||
|
||||
var scheme, serverURL string
|
||||
if len(parts) >= 2 {
|
||||
scheme = parts[0]
|
||||
serverURL = parts[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf("parse endpoint error")
|
||||
}
|
||||
|
||||
rocketChatSrv, err := rocketchat.New(serverURL, scheme, clientId, clientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rocketChatSrv.AddReceivers(channelName)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(rocketChatSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
30
notification/slack.go
Normal file
30
notification/slack.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/slack"
|
||||
)
|
||||
|
||||
func NewSlackProvider(apiToken string, channelID string) (*notify.Notify, error) {
|
||||
slackSrv := slack.New(apiToken)
|
||||
slackSrv.AddReceivers(channelID)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(slackSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
@@ -18,9 +18,9 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/telegram"
|
||||
api "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/nikoksr/notify/service/telegram"
|
||||
)
|
||||
|
||||
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
|
||||
@@ -28,15 +28,18 @@ func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &telegram.Telegram{}
|
||||
t.SetClient(client)
|
||||
telegramSrv := &telegram.Telegram{}
|
||||
telegramSrv.SetClient(client)
|
||||
|
||||
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.AddReceivers(chatId)
|
||||
telegramSrv.AddReceivers(chatId)
|
||||
|
||||
return t, nil
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(telegramSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
||||
|
41
notification/twitter.go
Normal file
41
notification/twitter.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/twitter"
|
||||
)
|
||||
|
||||
func NewTwitterProvider(consumerKey string, consumerSecret string, accessToken string, accessTokenSecret string, twitterId string) (*notify.Notify, error) {
|
||||
credentials := twitter.Credentials{
|
||||
ConsumerKey: consumerKey,
|
||||
ConsumerSecret: consumerSecret,
|
||||
AccessToken: accessToken,
|
||||
AccessTokenSecret: accessTokenSecret,
|
||||
}
|
||||
twitterSrv, err := twitter.NewWithHttpClient(credentials, proxy.ProxyHttpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
twitterSrv.AddReceivers(twitterId)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(twitterSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
36
notification/viber.go
Normal file
36
notification/viber.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/viber"
|
||||
)
|
||||
|
||||
func NewViberProvider(senderName string, appKey string, webhookURL string, receiverId string) (notify.Notifier, error) {
|
||||
viberSrv := viber.New(appKey, senderName, "")
|
||||
|
||||
err := viberSrv.SetWebhook(webhookURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
viberSrv.AddReceivers(receiverId)
|
||||
|
||||
notifier := notify.New()
|
||||
notifier.UseServices(viberSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
33
notification/webpush.go
Normal file
33
notification/webpush.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/casdoor/notify"
|
||||
"github.com/casdoor/notify/service/webpush"
|
||||
)
|
||||
|
||||
func NewWebpushProvider(publicKey string, privateKey string, endpoint string) (*notify.Notify, error) {
|
||||
webpushSrv := webpush.New(publicKey, privateKey)
|
||||
|
||||
subscription := webpush.Subscription{
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
webpushSrv.AddReceivers(subscription)
|
||||
|
||||
notifier := notify.NewWithServices(webpushSrv)
|
||||
|
||||
return notifier, nil
|
||||
}
|
@@ -428,15 +428,14 @@ func (application *Application) GetId() string {
|
||||
}
|
||||
|
||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||
isValid := false
|
||||
for _, targetUri := range application.RedirectUris {
|
||||
redirectUris := append([]string{"http://localhost:"}, application.RedirectUris...)
|
||||
for _, targetUri := range redirectUris {
|
||||
targetUriRegex := regexp.MustCompile(targetUri)
|
||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||
isValid = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
return isValid
|
||||
return false
|
||||
}
|
||||
|
||||
func IsOriginAllowed(origin string) (bool, error) {
|
||||
|
@@ -33,10 +33,8 @@ type Cert struct {
|
||||
BitSize int `json:"bitSize"`
|
||||
ExpireInYears int `json:"expireInYears"`
|
||||
|
||||
Certificate string `xorm:"mediumtext" json:"certificate"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
|
||||
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
|
||||
Certificate string `xorm:"mediumtext" json:"certificate"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
}
|
||||
|
||||
func GetMaskedCert(cert *Cert) *Cert {
|
||||
|
BIN
object/cert.go~
Normal file
BIN
object/cert.go~
Normal file
Binary file not shown.
@@ -350,7 +350,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
||||
return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation"))
|
||||
}
|
||||
|
||||
func CheckAccessPermission(userId string, application *Application) (bool, error) {
|
||||
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
||||
var err error
|
||||
if userId == "built-in/admin" {
|
||||
return true, nil
|
||||
@@ -361,32 +361,40 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
|
||||
return false, err
|
||||
}
|
||||
|
||||
allowed := true
|
||||
allowCount := 0
|
||||
denyCount := 0
|
||||
for _, permission := range permissions {
|
||||
if !permission.IsEnabled {
|
||||
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
isHit := false
|
||||
for _, resource := range permission.Resources {
|
||||
if application.Name == resource {
|
||||
isHit = true
|
||||
break
|
||||
}
|
||||
if permission.isUserHit(userId) {
|
||||
allowCount += 1
|
||||
}
|
||||
|
||||
if isHit {
|
||||
containsAsterisk := ContainsAsterisk(userId, permission.Users)
|
||||
if containsAsterisk {
|
||||
return true, err
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
|
||||
var isAllowed bool
|
||||
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if isAllowed {
|
||||
if permission.Effect == "Allow" {
|
||||
allowCount += 1
|
||||
}
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
|
||||
return allowed, err
|
||||
} else {
|
||||
if permission.Effect == "Deny" {
|
||||
denyCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed, err
|
||||
|
||||
if denyCount > 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func CheckUsername(username string, lang string) string {
|
||||
@@ -406,10 +414,6 @@ func CheckUsername(username string, lang string) string {
|
||||
}
|
||||
|
||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
if user.DisplayName == "" {
|
||||
return i18n.Translate(lang, "user:Display name cannot be empty")
|
||||
}
|
||||
|
||||
if oldUser.Name != user.Name {
|
||||
if msg := CheckUsername(user.Name, lang); msg != "" {
|
||||
return msg
|
||||
|
@@ -19,6 +19,7 @@ package object
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/casdoor/casdoor/email"
|
||||
"github.com/casdoor/gomail/v2"
|
||||
)
|
||||
|
||||
@@ -35,9 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
||||
}
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
dialer := getDialer(provider)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.AppId, provider.Host, provider.Port, provider.DisableSsl)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
@@ -49,14 +48,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
fromName = sender
|
||||
}
|
||||
|
||||
message.SetAddressHeader("From", fromAddress, fromName)
|
||||
message.SetHeader("To", dest)
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
message.SkipUsernameCheck = true
|
||||
|
||||
return dialer.DialAndSend(message)
|
||||
return emailProvider.Send(fromAddress, fromName, dest, title, content)
|
||||
}
|
||||
|
||||
// DailSmtpServer Dail Smtp server
|
||||
|
@@ -19,7 +19,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pquerna/otp"
|
||||
@@ -39,10 +38,11 @@ type TotpMfa struct {
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||
issuer := beego.AppConfig.String("appname")
|
||||
if issuer == "" {
|
||||
issuer = "casdoor"
|
||||
}
|
||||
//issuer := beego.AppConfig.String("appname")
|
||||
//if issuer == "" {
|
||||
// issuer = "casdoor"
|
||||
//}
|
||||
issuer := "Casdoor"
|
||||
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: issuer,
|
||||
@@ -81,12 +81,15 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
||||
return errors.New("totp secret is missing")
|
||||
}
|
||||
|
||||
result, _ := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
|
||||
result, err := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
|
||||
Period: MfaTotpPeriodInSeconds,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result {
|
||||
return nil
|
||||
@@ -125,7 +128,15 @@ func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Verify(passcode string) error {
|
||||
result := totp.Validate(passcode, mfa.Config.Secret)
|
||||
result, err := totp.ValidateCustom(passcode, mfa.Config.Secret, time.Now().UTC(), totp.ValidateOpts{
|
||||
Period: MfaTotpPeriodInSeconds,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result {
|
||||
return nil
|
||||
|
@@ -18,12 +18,12 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/casdoor/casdoor/notification"
|
||||
"github.com/nikoksr/notify"
|
||||
"github.com/casdoor/notify"
|
||||
)
|
||||
|
||||
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
||||
var client notify.Notifier
|
||||
client, err := notification.GetNotificationProvider(provider.Type, provider.AppId, provider.Receiver, provider.Method, provider.Title)
|
||||
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
|
||||
@@ -55,6 +54,7 @@ type Payment struct {
|
||||
// Order Info
|
||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success
|
||||
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
}
|
||||
@@ -152,7 +152,7 @@ func DeletePayment(payment *Payment) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, *pp.NotifyResult, error) {
|
||||
func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) {
|
||||
payment, err := getPayment(owner, paymentName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -166,7 +166,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pProvider, cert, err := provider.getPaymentProvider()
|
||||
pProvider, err := GetPaymentProvider(provider)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -180,11 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if orderId == "" {
|
||||
orderId = payment.OutOrderId
|
||||
}
|
||||
|
||||
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
|
||||
notifyResult, err := pProvider.Notify(body, payment.OutOrderId)
|
||||
if err != nil {
|
||||
return payment, nil, err
|
||||
}
|
||||
@@ -205,8 +201,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
|
||||
return payment, notifyResult, nil
|
||||
}
|
||||
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) {
|
||||
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName, orderId)
|
||||
func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, error) {
|
||||
payment, notifyResult, err := notifyPayment(body, owner, paymentName)
|
||||
if payment != nil {
|
||||
if err != nil {
|
||||
payment.State = pp.PaymentStateError
|
||||
@@ -234,7 +230,7 @@ func invoicePayment(payment *Payment) (string, error) {
|
||||
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
||||
}
|
||||
|
||||
pProvider, _, err := provider.getPaymentProvider()
|
||||
pProvider, err := GetPaymentProvider(provider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ type Permission struct {
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
Domains []string `xorm:"mediumtext" json:"domains"`
|
||||
|
||||
@@ -60,10 +61,6 @@ type PermissionRule struct {
|
||||
|
||||
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
|
||||
|
||||
func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func GetPermissionCount(owner, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Permission{})
|
||||
@@ -345,20 +342,6 @@ func GetPermissionsByModel(owner string, model string) ([]*Permission, error) {
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func ContainsAsterisk(userId string, users []string) bool {
|
||||
containsAsterisk := false
|
||||
group, _ := util.GetOwnerAndNameFromId(userId)
|
||||
for _, user := range users {
|
||||
permissionGroup, permissionUserName := util.GetOwnerAndNameFromId(user)
|
||||
if permissionGroup == group && permissionUserName == "*" {
|
||||
containsAsterisk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return containsAsterisk
|
||||
}
|
||||
|
||||
func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||
for _, permission := range permissions {
|
||||
permission.Users = nil
|
||||
@@ -388,3 +371,27 @@ func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]stri
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *Permission) isUserHit(name string) bool {
|
||||
targetOrg, _ := util.GetOwnerAndNameFromId(name)
|
||||
for _, user := range p.Users {
|
||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||
if userOrg == targetOrg && userName == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Permission) isResourceHit(name string) bool {
|
||||
for _, resource := range p.Resources {
|
||||
if name == resource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ type Product struct {
|
||||
Price float64 `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Sold int `json:"sold"`
|
||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||
Providers []string `xorm:"varchar(255)" json:"providers"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
@@ -158,24 +158,23 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (string, string, error) {
|
||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
|
||||
product, err := GetProduct(id)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if product == nil {
|
||||
return "", "", fmt.Errorf("the product: %s does not exist", id)
|
||||
return nil, fmt.Errorf("the product: %s does not exist", id)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pProvider, _, err := provider.getPaymentProvider()
|
||||
pProvider, err := GetPaymentProvider(provider)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
@@ -192,15 +191,15 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
if pricingName != "" && planName != "" {
|
||||
plan, err := GetPlan(util.GetId(owner, planName))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
if plan == nil {
|
||||
return "", "", fmt.Errorf("the plan: %s does not exist", planName)
|
||||
return nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||
}
|
||||
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
||||
_, err = AddSubscription(sub)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
||||
}
|
||||
@@ -208,10 +207,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
// Create an OrderId and get the payUrl
|
||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
// Create a Payment linked with Product and Order
|
||||
payment := Payment{
|
||||
payment := &Payment{
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
@@ -230,6 +229,7 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
|
||||
User: user.Name,
|
||||
PayUrl: payUrl,
|
||||
SuccessUrl: returnUrl,
|
||||
State: pp.PaymentStateCreated,
|
||||
OutOrderId: orderId,
|
||||
}
|
||||
@@ -238,15 +238,15 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
payment.State = pp.PaymentStatePaid
|
||||
}
|
||||
|
||||
affected, err := AddPayment(&payment)
|
||||
affected, err := AddPayment(payment)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !affected {
|
||||
return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
}
|
||||
return payUrl, orderId, err
|
||||
return payment, err
|
||||
}
|
||||
|
||||
func ExtendProductWithProviders(product *Product) error {
|
||||
|
@@ -17,31 +17,24 @@
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func TestProduct(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
product, _ := GetProduct("admin/product_123")
|
||||
provider, _ := getProvider(product.Owner, "provider_pay_alipay")
|
||||
cert, _ := getCert(product.Owner, "cert-pay-alipay")
|
||||
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
notifyUrl := ""
|
||||
payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println(payUrl)
|
||||
}
|
||||
//func TestProduct(t *testing.T) {
|
||||
// InitConfig()
|
||||
//
|
||||
// product, _ := GetProduct("admin/product_123")
|
||||
// provider, _ := getProvider(product.Owner, "provider_pay_alipay")
|
||||
// cert, _ := getCert(product.Owner, "cert-pay-alipay")
|
||||
// pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// paymentName := util.GenerateTimeId()
|
||||
// returnUrl := ""
|
||||
// notifyUrl := ""
|
||||
// payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// println(payUrl)
|
||||
//}
|
||||
|
@@ -251,30 +251,69 @@ func DeleteProvider(provider *Provider) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
||||
cert := &Cert{}
|
||||
if p.Cert != "" {
|
||||
var err error
|
||||
cert, err = getCert(p.Owner, p.Cert)
|
||||
cert, err = GetCert(util.GetId(p.Owner, p.Cert))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||
return nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||
}
|
||||
}
|
||||
|
||||
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2)
|
||||
if err != nil {
|
||||
return nil, cert, err
|
||||
typ := p.Type
|
||||
if typ == "Dummy" {
|
||||
pp, err := pp.NewDummyPaymentProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "Alipay" {
|
||||
if p.Metadata != "" {
|
||||
// alipay provider store rootCert's name in metadata
|
||||
rootCert, err := GetCert(util.GetId(p.Owner, p.Metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rootCert == nil {
|
||||
return nil, fmt.Errorf("the cert: %s does not exist", p.Metadata)
|
||||
}
|
||||
pp, err := pp.NewAlipayPaymentProvider(p.ClientId, cert.Certificate, cert.PrivateKey, rootCert.Certificate, rootCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("the metadata of alipay provider is empty")
|
||||
}
|
||||
} else if typ == "GC" {
|
||||
return pp.NewGcPaymentProvider(p.ClientId, p.ClientSecret, p.Host), nil
|
||||
} else if typ == "WeChat Pay" {
|
||||
pp, err := pp.NewWechatPaymentProvider(p.ClientId, p.ClientSecret, p.ClientId2, cert.Certificate, cert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "PayPal" {
|
||||
pp, err := pp.NewPaypalPaymentProvider(p.ClientId, p.ClientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "Stripe" {
|
||||
pp, err := pp.NewStripePaymentProvider(p.ClientId, p.ClientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
if pProvider == nil {
|
||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
return pProvider, cert, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetId() string {
|
||||
@@ -376,6 +415,8 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
||||
providerInfo.ClientId = provider.ClientId2
|
||||
providerInfo.ClientSecret = provider.ClientSecret2
|
||||
}
|
||||
} else if provider.Type == "AzureAD" {
|
||||
providerInfo.HostUrl = provider.Domain
|
||||
}
|
||||
|
||||
return providerInfo
|
||||
|
@@ -72,7 +72,7 @@ func GetTruncatedPath(provider *Provider, fullFilePath string, limit int) string
|
||||
}
|
||||
|
||||
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath))
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, fullFilePath)
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||
|
||||
host := ""
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,7 +32,7 @@ type Credential struct {
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||
var results []map[string]string
|
||||
var results []map[string]sql.NullString
|
||||
err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -196,7 +197,7 @@ func (syncer *Syncer) getUserValue(user *User, key string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
|
||||
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]sql.NullString) []*OriginalUser {
|
||||
users := []*OriginalUser{}
|
||||
for _, result := range results {
|
||||
originalUser := &OriginalUser{
|
||||
@@ -216,11 +217,11 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
||||
names := strings.Split(tableColumnName, "+")
|
||||
var values []string
|
||||
for _, name := range names {
|
||||
values = append(values, result[strings.Trim(name, " ")])
|
||||
values = append(values, result[strings.Trim(name, " ")].String)
|
||||
}
|
||||
value = strings.Join(values, " ")
|
||||
} else {
|
||||
value = result[tableColumnName]
|
||||
value = result[tableColumnName].String
|
||||
}
|
||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
|
||||
}
|
||||
@@ -249,9 +250,9 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
||||
// enable
|
||||
value, ok := result["ENABLED"]
|
||||
if ok {
|
||||
originalUser.IsForbidden = !util.ParseBool(value)
|
||||
originalUser.IsForbidden = !util.ParseBool(value.String)
|
||||
} else {
|
||||
originalUser.IsForbidden = !util.ParseBool(result["enabled"])
|
||||
originalUser.IsForbidden = !util.ParseBool(result["enabled"].String)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -185,38 +185,57 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
|
||||
}
|
||||
|
||||
func GenerateCasToken(userId string, service string) (string, error) {
|
||||
if user, err := GetUser(userId); err != nil {
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if user != nil {
|
||||
authenticationSuccess := CasAuthenticationSuccess{
|
||||
User: user.Name,
|
||||
Attributes: &CasAttributes{
|
||||
AuthenticationDate: time.Now(),
|
||||
UserAttributes: &CasUserAttributes{},
|
||||
},
|
||||
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
||||
}
|
||||
data, _ := json.Marshal(user)
|
||||
tmp := map[string]string{}
|
||||
json.Unmarshal(data, &tmp)
|
||||
for k, v := range tmp {
|
||||
if v != "" {
|
||||
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
st := fmt.Sprintf("ST-%d", rand.Int())
|
||||
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||
AuthenticationSuccess: &authenticationSuccess,
|
||||
Service: service,
|
||||
UserId: userId,
|
||||
})
|
||||
return st, nil
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid user Id")
|
||||
}
|
||||
if user == nil {
|
||||
return "", fmt.Errorf("The user: %s doesn't exist", userId)
|
||||
}
|
||||
|
||||
user, _ = GetMaskedUser(user, false)
|
||||
|
||||
authenticationSuccess := CasAuthenticationSuccess{
|
||||
User: user.Name,
|
||||
Attributes: &CasAttributes{
|
||||
AuthenticationDate: time.Now(),
|
||||
UserAttributes: &CasUserAttributes{},
|
||||
},
|
||||
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmp := map[string]interface{}{}
|
||||
err = json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for k, v := range tmp {
|
||||
value := fmt.Sprintf("%v", v)
|
||||
if value == "<nil>" || value == "[]" || value == "map[]" {
|
||||
value = ""
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||
Name: k,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
st := fmt.Sprintf("ST-%d", rand.Int())
|
||||
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||
AuthenticationSuccess: &authenticationSuccess,
|
||||
Service: service,
|
||||
UserId: userId,
|
||||
})
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// GetValidationBySaml
|
||||
|
@@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -194,16 +195,17 @@ type User struct {
|
||||
}
|
||||
|
||||
type Userinfo struct {
|
||||
Sub string `json:"sub"`
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Avatar string `json:"picture,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Sub string `json:"sub"`
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
Avatar string `json:"picture,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
type ManagedAccount struct {
|
||||
@@ -482,7 +484,7 @@ func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetLastUser(owner string) (*User, error) {
|
||||
func getLastUser(owner string) (*User, error) {
|
||||
user := User{Owner: owner}
|
||||
existed, err := ormer.Engine.Desc("created_time", "id").Get(&user)
|
||||
if err != nil {
|
||||
@@ -613,9 +615,18 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
}
|
||||
|
||||
func AddUser(user *User) (bool, error) {
|
||||
var err error
|
||||
if user.Id == "" {
|
||||
user.Id = util.GenerateId()
|
||||
application, err := GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
id, err := GenerateIdForNewUser(application)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
user.Id = id
|
||||
}
|
||||
|
||||
if user.Owner == "" || user.Name == "" {
|
||||
@@ -631,7 +642,7 @@ func AddUser(user *User) (bool, error) {
|
||||
user.UpdateUserPassword(organization)
|
||||
}
|
||||
|
||||
err = user.UpdateUserHash()
|
||||
err := user.UpdateUserHash()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -757,6 +768,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
|
||||
}
|
||||
if strings.Contains(scope, "email") {
|
||||
resp.Email = user.Email
|
||||
resp.EmailVerified = user.EmailVerified
|
||||
}
|
||||
if strings.Contains(scope, "address") {
|
||||
resp.Address = user.Location
|
||||
@@ -898,3 +910,22 @@ func (user *User) IsGlobalAdmin() bool {
|
||||
|
||||
return user.Owner == "built-in"
|
||||
}
|
||||
|
||||
func GenerateIdForNewUser(application *Application) (string, error) {
|
||||
if application.GetSignupItemRule("ID") != "Incremental" {
|
||||
return util.GenerateId(), nil
|
||||
}
|
||||
|
||||
lastUser, err := getLastUser(application.Organization)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lastUserId := -1
|
||||
if lastUser != nil {
|
||||
lastUserId = util.ParseInt(lastUser.Id)
|
||||
}
|
||||
|
||||
res := strconv.Itoa(lastUserId + 1)
|
||||
return res, nil
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") {
|
||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") {
|
||||
return nil, "", nil
|
||||
} else {
|
||||
return nil, "", err
|
||||
|
@@ -87,7 +87,7 @@ func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
|
||||
|
||||
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
|
||||
if err != nil {
|
||||
if err == errors.ERR_NAME_NOT_FOUND {
|
||||
if err == errors.ErrNameNotFound {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
|
@@ -320,6 +320,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.Score != newUser.Score {
|
||||
item := GetAccountItemByName("Score", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass {
|
||||
return pass, err
|
||||
|
75
pp/alipay.go
75
pp/alipay.go
@@ -16,9 +16,9 @@ package pp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
)
|
||||
@@ -28,6 +28,11 @@ type AlipayPaymentProvider struct {
|
||||
}
|
||||
|
||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
|
||||
// clientId => appId
|
||||
// cert.Certificate => appCertificate
|
||||
// cert.PrivateKey => appPrivateKey
|
||||
// rootCert.Certificate => authorityPublicKey
|
||||
// rootCert.PrivateKey => authorityRootPublicKey
|
||||
pp := &AlipayPaymentProvider{}
|
||||
|
||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||
@@ -46,54 +51,60 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
|
||||
bm.Set("providerName", providerName)
|
||||
bm.Set("productName", productName)
|
||||
|
||||
bm.Set("return_url", returnUrl)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
|
||||
bm.Set("subject", productDisplayName)
|
||||
pp.Client.SetReturnUrl(returnUrl)
|
||||
pp.Client.SetNotifyUrl(notifyUrl)
|
||||
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("total_amount", getPriceString(price))
|
||||
bm.Set("total_amount", priceFloat64ToString(price))
|
||||
|
||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return payUrl, "", nil
|
||||
return payUrl, paymentName, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
bm := gopay.BodyMap{}
|
||||
bm.Set("out_trade_no", orderId)
|
||||
aliRsp, err := pp.Client.TradeQuery(context.Background(), bm)
|
||||
notifyResult := &NotifyResult{}
|
||||
if err != nil {
|
||||
errRsp := &alipay.ErrorResponse{}
|
||||
unmarshalErr := json.Unmarshal([]byte(err.Error()), errRsp)
|
||||
if unmarshalErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
if errRsp.SubCode == "ACQ.TRADE_NOT_EXIST" {
|
||||
notifyResult.PaymentStatus = PaymentStateCanceled
|
||||
return notifyResult, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providerName := bm.Get("providerName")
|
||||
productName := bm.Get("productName")
|
||||
|
||||
productDisplayName := bm.Get("subject")
|
||||
paymentName := bm.Get("out_trade_no")
|
||||
price := util.ParseFloat(bm.Get("total_amount"))
|
||||
|
||||
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch aliRsp.Response.TradeStatus {
|
||||
case "WAIT_BUYER_PAY":
|
||||
notifyResult.PaymentStatus = PaymentStateCreated
|
||||
return notifyResult, nil
|
||||
case "TRADE_CLOSED":
|
||||
notifyResult.PaymentStatus = PaymentStateTimeout
|
||||
return notifyResult, nil
|
||||
case "TRADE_SUCCESS":
|
||||
// skip
|
||||
default:
|
||||
notifyResult.PaymentStatus = PaymentStateError
|
||||
notifyResult.NotifyMessage = fmt.Sprintf("unexpected alipay trade state: %v", aliRsp.Response.TradeStatus)
|
||||
return notifyResult, nil
|
||||
}
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
notifyResult := &NotifyResult{
|
||||
productDisplayName, productName, providerName, _ := parseAttachString(aliRsp.Response.Subject)
|
||||
notifyResult = &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OrderId: orderId,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
Price: price,
|
||||
PaymentName: paymentName,
|
||||
Price: priceStringToFloat64(aliRsp.Response.TotalAmount),
|
||||
PaymentName: orderId,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
@@ -14,8 +14,6 @@
|
||||
|
||||
package pp
|
||||
|
||||
import "net/http"
|
||||
|
||||
type DummyPaymentProvider struct{}
|
||||
|
||||
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
||||
@@ -27,7 +25,7 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay
|
||||
return returnUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
return &NotifyResult{
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
}, nil
|
||||
|
2
pp/gc.go
2
pp/gc.go
@@ -216,7 +216,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
||||
return payRespInfo.PayUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
reqBody := GcRequestBody{}
|
||||
m, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
|
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -88,7 +87,7 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
|
||||
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
notifyResult := &NotifyResult{}
|
||||
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
||||
if err != nil {
|
||||
|
@@ -14,8 +14,6 @@
|
||||
|
||||
package pp
|
||||
|
||||
import "net/http"
|
||||
|
||||
type PaymentState string
|
||||
|
||||
const (
|
||||
@@ -42,45 +40,7 @@ type NotifyResult struct {
|
||||
|
||||
type PaymentProvider interface {
|
||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error)
|
||||
Notify(body []byte, orderId string) (*NotifyResult, error)
|
||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||
GetResponseError(err error) string
|
||||
}
|
||||
|
||||
func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
||||
if typ == "Dummy" {
|
||||
pp, err := NewDummyPaymentProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "Alipay" {
|
||||
pp, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "GC" {
|
||||
return NewGcPaymentProvider(clientId, clientSecret, host), nil
|
||||
} else if typ == "WeChat Pay" {
|
||||
pp, err := NewWechatPaymentProvider(clientId, clientSecret, clientId2, appCertificate, appPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "PayPal" {
|
||||
pp, err := NewPaypalPaymentProvider(clientId, clientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "Stripe" {
|
||||
pp, err := NewStripePaymentProvider(clientId, clientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ package pp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -94,7 +93,7 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
|
||||
return sCheckout.URL, sCheckout.ID, nil
|
||||
}
|
||||
|
||||
func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
notifyResult := &NotifyResult{}
|
||||
sCheckout, err := stripeCheckout.Get(orderId, nil)
|
||||
if err != nil {
|
||||
|
@@ -49,3 +49,11 @@ func priceFloat64ToInt64(price float64) int64 {
|
||||
func priceFloat64ToString(price float64) string {
|
||||
return strconv.FormatFloat(price, 'f', 2, 64)
|
||||
}
|
||||
|
||||
func priceStringToFloat64(price string) float64 {
|
||||
f, err := strconv.ParseFloat(price, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ package pp
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay"
|
||||
@@ -31,17 +31,22 @@ type WechatPayNotifyResponse struct {
|
||||
|
||||
type WechatPaymentProvider struct {
|
||||
Client *wechat.ClientV3
|
||||
appId string
|
||||
AppId string
|
||||
}
|
||||
|
||||
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCertSerialNumber string, privateKey string) (*WechatPaymentProvider, error) {
|
||||
if appId == "" && mchId == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" {
|
||||
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, serialNo string, privateKey string) (*WechatPaymentProvider, error) {
|
||||
// https://pay.weixin.qq.com/docs/merchant/products/native-payment/preparation.html
|
||||
// clientId => mchId
|
||||
// clientSecret => apiV3Key
|
||||
// clientId2 => appId
|
||||
|
||||
// appCertificate => serialNo
|
||||
// appPrivateKey => privateKey
|
||||
if appId == "" || mchId == "" || serialNo == "" || apiV3Key == "" || privateKey == "" {
|
||||
return &WechatPaymentProvider{}, nil
|
||||
}
|
||||
|
||||
pp := &WechatPaymentProvider{appId: appId}
|
||||
|
||||
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
||||
clientV3, err := wechat.NewClientV3(mchId, serialNo, apiV3Key, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -50,73 +55,70 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pp.Client = clientV3.SetPlatformCert([]byte(platformCert), serialNo)
|
||||
pp := &WechatPaymentProvider{
|
||||
Client: clientV3.SetPlatformCert([]byte(platformCert), serialNo),
|
||||
AppId: appId,
|
||||
}
|
||||
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
|
||||
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
||||
bm.Set("appid", pp.appId)
|
||||
bm.Set("appid", pp.AppId)
|
||||
bm.Set("description", productDisplayName)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", int(price*100))
|
||||
bm.Set("currency", "CNY")
|
||||
bm.Set("total", priceFloat64ToInt64(price))
|
||||
bm.Set("currency", currency)
|
||||
})
|
||||
|
||||
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if wxRsp.Code != wechat.Success {
|
||||
return "", "", errors.New(wxRsp.Error)
|
||||
if nativeRsp.Code != wechat.Success {
|
||||
return "", "", errors.New(nativeRsp.Error)
|
||||
}
|
||||
|
||||
return wxRsp.Response.CodeUrl, "", nil
|
||||
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
notifyResult := &NotifyResult{}
|
||||
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := pp.Client.WxPublicKey()
|
||||
err = notifyReq.VerifySignByPK(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if queryRsp.Code != wechat.Success {
|
||||
return nil, errors.New(queryRsp.Error)
|
||||
}
|
||||
|
||||
apiKey := string(pp.Client.ApiV3Key)
|
||||
result, err := notifyReq.DecryptCipherText(apiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch queryRsp.Response.TradeState {
|
||||
case "SUCCESS":
|
||||
// skip
|
||||
case "CLOSED":
|
||||
notifyResult.PaymentStatus = PaymentStateCanceled
|
||||
return notifyResult, nil
|
||||
case "NOTPAY", "USERPAYING": // not-pad: waiting for user to pay; user-paying: user is paying
|
||||
notifyResult.PaymentStatus = PaymentStateCreated
|
||||
return notifyResult, nil
|
||||
default:
|
||||
notifyResult.PaymentStatus = PaymentStateError
|
||||
notifyResult.NotifyMessage = fmt.Sprintf("unexpected wechat trade state: %v", queryRsp.Response.TradeState)
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
||||
paymentName := result.OutTradeNo
|
||||
price := float64(result.Amount.PayerTotal) / 100
|
||||
|
||||
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifyResult := &NotifyResult{
|
||||
productDisplayName, productName, providerName, _ := parseAttachString(queryRsp.Response.Attach)
|
||||
notifyResult = &NotifyResult{
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
OrderId: orderId,
|
||||
Price: price,
|
||||
Price: priceInt64ToFloat64(int64(queryRsp.Response.Amount.Total)),
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
PaymentName: paymentName,
|
||||
PaymentName: queryRsp.Response.OutTradeNo,
|
||||
}
|
||||
return notifyResult, nil
|
||||
}
|
||||
|
@@ -29,7 +29,13 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
|
||||
// GET parameter like "/page?access_token=123" or
|
||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
||||
accessToken := ctx.Input.Query("accessToken")
|
||||
if accessToken == "" {
|
||||
accessToken = ctx.Input.Query("access_token")
|
||||
}
|
||||
if accessToken == "" {
|
||||
accessToken = parseBearerToken(ctx)
|
||||
}
|
||||
|
||||
if accessToken != "" {
|
||||
token, err := object.GetTokenByAccessToken(accessToken)
|
||||
|
@@ -40,6 +40,13 @@ func CorsFilter(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Request.RequestURI == "/api/userinfo" {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
return
|
||||
}
|
||||
|
||||
if origin != "" && originConf != "" && origin != originConf {
|
||||
ok, err := object.IsOriginAllowed(origin)
|
||||
if err != nil {
|
||||
|
@@ -63,6 +63,7 @@ func initAPI() {
|
||||
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
|
||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
||||
|
||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||
@@ -247,10 +248,10 @@ func initAPI() {
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
|
||||
|
||||
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin")
|
||||
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish")
|
||||
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "Get:WebAuthnSigninBegin")
|
||||
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
|
||||
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "GET:WebAuthnSignupBegin")
|
||||
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "POST:WebAuthnSignupFinish")
|
||||
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "GET:WebAuthnSigninBegin")
|
||||
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "POST:WebAuthnSigninFinish")
|
||||
|
||||
beego.Router("/api/mfa/setup/initiate", &controllers.ApiController{}, "POST:MfaSetupInitiate")
|
||||
beego.Router("/api/mfa/setup/verify", &controllers.ApiController{}, "POST:MfaSetupVerify")
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,76 +24,69 @@ import (
|
||||
"github.com/casdoor/oss"
|
||||
)
|
||||
|
||||
var baseFolder = "files"
|
||||
|
||||
// FileSystem file system storage
|
||||
type FileSystem struct {
|
||||
Base string
|
||||
// LocalFileSystemProvider file system storage
|
||||
type LocalFileSystemProvider struct {
|
||||
BaseDir string
|
||||
}
|
||||
|
||||
// NewFileSystem initialize the local file system storage
|
||||
func NewFileSystem(base string) *FileSystem {
|
||||
absBase, err := filepath.Abs(base)
|
||||
// NewLocalFileSystemStorageProvider initialize the local file system storage
|
||||
func NewLocalFileSystemStorageProvider() *LocalFileSystemProvider {
|
||||
baseFolder := "files"
|
||||
absBase, err := filepath.Abs(baseFolder)
|
||||
if err != nil {
|
||||
panic("local file system storage's base folder is not initialized")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &FileSystem{Base: absBase}
|
||||
return &LocalFileSystemProvider{BaseDir: absBase}
|
||||
}
|
||||
|
||||
// GetFullPath get full path from absolute/relative path
|
||||
func (fileSystem FileSystem) GetFullPath(path string) string {
|
||||
func (sp LocalFileSystemProvider) GetFullPath(path string) string {
|
||||
fullPath := path
|
||||
if !strings.HasPrefix(path, fileSystem.Base) {
|
||||
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path))
|
||||
if !strings.HasPrefix(path, sp.BaseDir) {
|
||||
fullPath, _ = filepath.Abs(filepath.Join(sp.BaseDir, path))
|
||||
}
|
||||
return fullPath
|
||||
}
|
||||
|
||||
// Get receive file with given path
|
||||
func (fileSystem FileSystem) Get(path string) (*os.File, error) {
|
||||
return os.Open(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) Get(path string) (*os.File, error) {
|
||||
return os.Open(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// GetStream get file as stream
|
||||
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) {
|
||||
return os.Open(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) GetStream(path string) (io.ReadCloser, error) {
|
||||
return os.Open(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// Put store a reader into given path
|
||||
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) {
|
||||
var (
|
||||
fullPath = fileSystem.GetFullPath(path)
|
||||
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||
)
|
||||
func (sp LocalFileSystemProvider) Put(path string, reader io.Reader) (*oss.Object, error) {
|
||||
fullPath := sp.GetFullPath(path)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Casdoor fails to create folder: \"%s\" for local file system storage provider: %s. Make sure Casdoor process has correct permission to create/access it, or you can create it manually in advance", filepath.Dir(fullPath), err.Error())
|
||||
}
|
||||
|
||||
dst, err := os.Create(filepath.Clean(fullPath))
|
||||
|
||||
if err == nil {
|
||||
if seeker, ok := reader.(io.ReadSeeker); ok {
|
||||
seeker.Seek(0, 0)
|
||||
}
|
||||
_, err = io.Copy(dst, reader)
|
||||
}
|
||||
|
||||
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
|
||||
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: sp}, err
|
||||
}
|
||||
|
||||
// Delete delete file
|
||||
func (fileSystem FileSystem) Delete(path string) error {
|
||||
return os.Remove(fileSystem.GetFullPath(path))
|
||||
func (sp LocalFileSystemProvider) Delete(path string) error {
|
||||
return os.Remove(sp.GetFullPath(path))
|
||||
}
|
||||
|
||||
// List list all objects under current path
|
||||
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
var (
|
||||
objects []*oss.Object
|
||||
fullPath = fileSystem.GetFullPath(path)
|
||||
)
|
||||
func (sp LocalFileSystemProvider) List(path string) ([]*oss.Object, error) {
|
||||
objects := []*oss.Object{}
|
||||
fullPath := sp.GetFullPath(path)
|
||||
|
||||
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||
if path == fullPath {
|
||||
@@ -102,10 +96,10 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
if err == nil && !info.IsDir() {
|
||||
modTime := info.ModTime()
|
||||
objects = append(objects, &oss.Object{
|
||||
Path: strings.TrimPrefix(path, fileSystem.Base),
|
||||
Path: strings.TrimPrefix(path, sp.BaseDir),
|
||||
Name: info.Name(),
|
||||
LastModified: &modTime,
|
||||
StorageInterface: fileSystem,
|
||||
StorageInterface: sp,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
@@ -114,16 +108,12 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// GetEndpoint get endpoint, FileSystem's endpoint is /
|
||||
func (fileSystem FileSystem) GetEndpoint() string {
|
||||
// GetEndpoint get endpoint, LocalFileSystemProvider's endpoint is /
|
||||
func (sp LocalFileSystemProvider) GetEndpoint() string {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// GetURL get public accessible URL
|
||||
func (fileSystem FileSystem) GetURL(path string) (url string, err error) {
|
||||
func (sp LocalFileSystemProvider) GetURL(path string) (url string, err error) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func NewLocalFileSystemStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||
return NewFileSystem(baseFolder)
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import "github.com/casdoor/oss"
|
||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||
switch providerType {
|
||||
case "Local File System":
|
||||
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
return NewLocalFileSystemStorageProvider()
|
||||
case "AWS S3":
|
||||
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "MinIO":
|
||||
|
@@ -187,32 +187,6 @@ func IsStringsEmpty(strs ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func GetMaxLenStr(strs ...string) string {
|
||||
m := 0
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l > m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
||||
|
||||
func GetMinLenStr(strs ...string) string {
|
||||
m := int(^uint(0) >> 1)
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l < m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
||||
|
||||
func ReadStringFromPath(path string) string {
|
||||
data, err := os.ReadFile(filepath.Clean(path))
|
||||
if err != nil {
|
||||
@@ -326,3 +300,12 @@ func GetValueFromDataSourceName(key string, dataSourceName string) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetUsernameFromEmail(email string) string {
|
||||
tokens := strings.Split(email, "@")
|
||||
if len(tokens) == 0 {
|
||||
return uuid.NewString()
|
||||
} else {
|
||||
return tokens[0]
|
||||
}
|
||||
}
|
||||
|
@@ -189,45 +189,6 @@ func TestIsStrsEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMaxLenStr(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return casdoor", []string{"", "casdoor", "casbin"}, "casdoor"},
|
||||
{"Should be return casdoor_jdk", []string{"", "casdoor", "casbin", "casdoor_jdk"}, "casdoor_jdk"},
|
||||
{"Should be return empty string", []string{""}, ""},
|
||||
}
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetMaxLenStr(scenery.input...)
|
||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMinLenStr(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return casbin", []string{"casdoor", "casbin"}, "casbin"},
|
||||
{"Should be return casbin", []string{"casdoor", "casbin", "casdoor_jdk"}, "casbin"},
|
||||
{"Should be return empty string", []string{"a", "", "casbin"}, ""},
|
||||
{"Should be return a", []string{"a", "casdoor", "casbin"}, "a"},
|
||||
{"Should be return a", []string{"casdoor", "a", "casbin"}, "a"},
|
||||
{"Should be return a", []string{"casbin", "casdoor", "a"}, "a"},
|
||||
}
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetMinLenStr(scenery.input...)
|
||||
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnakeString(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
|
@@ -34,6 +34,7 @@
|
||||
"i18next": "^19.8.9",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
@@ -82,6 +83,9 @@
|
||||
"@babel/eslint-parser": "^7.18.9",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^12.5.1",
|
||||
"eslint": "8.22.0",
|
||||
@@ -91,10 +95,7 @@
|
||||
"lint-staged": "^13.0.3",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-recommended-less": "^1.0.4",
|
||||
"stylelint-config-standard": "^28.0.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2"
|
||||
"stylelint-config-standard": "^28.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{css,less}": [
|
||||
|
@@ -664,7 +664,8 @@ class App extends Component {
|
||||
window.location.pathname.startsWith("/cas") ||
|
||||
window.location.pathname.startsWith("/auto-signup") ||
|
||||
window.location.pathname.startsWith("/select-plan") ||
|
||||
window.location.pathname.startsWith("/buy-plan");
|
||||
window.location.pathname.startsWith("/buy-plan") ||
|
||||
window.location.pathname.startsWith("/qrcode") ;
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
|
@@ -542,7 +542,7 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("signup:Terms of Use"), i18next.t("signup:Terms of Use - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("termsOfUse", e.target.value);
|
||||
}} />
|
||||
<Upload maxCount={1} accept=".html" showUploadList={false}
|
||||
|
@@ -204,7 +204,7 @@ class BaseListPage extends React.Component {
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
<Tour
|
||||
open={this.state.isTourVisible}
|
||||
open={Setting.isMobile() ? false : this.state.isTourVisible}
|
||||
onClose={this.setIsTourVisible}
|
||||
steps={this.getSteps()}
|
||||
indicatorsRender={(current, total) => (
|
||||
|
@@ -31,6 +31,7 @@ import CasLogout from "./auth/CasLogout";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import QrCodePage from "./QrCodePage";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -113,6 +114,7 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
@@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
|
||||
payment: payment,
|
||||
});
|
||||
if (payment.state === "Created") {
|
||||
if (["PayPal", "Stripe"].includes(payment.type)) {
|
||||
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
|
||||
this.setState({
|
||||
timeout: setTimeout(async() => {
|
||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||
|
@@ -17,6 +17,7 @@ import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
|
||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import * as RoleBackend from "./backend/RoleBackend";
|
||||
@@ -35,6 +36,7 @@ class PermissionEditPage extends React.Component {
|
||||
organizations: [],
|
||||
model: null,
|
||||
users: [],
|
||||
groups: [],
|
||||
roles: [],
|
||||
models: [],
|
||||
resources: [],
|
||||
@@ -67,6 +69,7 @@ class PermissionEditPage extends React.Component {
|
||||
});
|
||||
|
||||
this.getUsers(permission.owner);
|
||||
this.getGroups(permission.owner);
|
||||
this.getRoles(permission.owner);
|
||||
this.getModels(permission.owner);
|
||||
this.getResources(permission.owner);
|
||||
@@ -97,6 +100,20 @@ class PermissionEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getGroups(organizationName) {
|
||||
GroupBackend.getGroups(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getRoles(organizationName) {
|
||||
RoleBackend.getRoles(organizationName)
|
||||
.then((res) => {
|
||||
@@ -192,6 +209,7 @@ class PermissionEditPage extends React.Component {
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.permission.owner} onChange={(owner => {
|
||||
this.updatePermissionField("owner", owner);
|
||||
this.getUsers(owner);
|
||||
this.getGroups(owner);
|
||||
this.getRoles(owner);
|
||||
this.getModels(owner);
|
||||
this.getResources(owner);
|
||||
@@ -263,6 +281,17 @@ class PermissionEditPage extends React.Component {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("role:Sub groups"), i18next.t("role:Sub groups - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.groups}
|
||||
onChange={(value => {this.updatePermissionField("groups", value);})}
|
||||
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||
|
@@ -33,6 +33,7 @@ class PermissionListPage extends BaseListPage {
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Permission - ${randomName}`,
|
||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||
groups: [],
|
||||
roles: [],
|
||||
domains: [],
|
||||
resourceType: "Application",
|
||||
@@ -179,6 +180,17 @@ class PermissionListPage extends BaseListPage {
|
||||
return Setting.getTags(text, "users");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("role:Sub groups"),
|
||||
dataIndex: "groups",
|
||||
key: "groups",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("groups"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text, "groups");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("role:Sub roles"),
|
||||
dataIndex: "roles",
|
||||
|
@@ -13,8 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Descriptions, Modal, Spin} from "antd";
|
||||
import {CheckCircleTwoTone} from "@ant-design/icons";
|
||||
import {Button, Descriptions, Spin} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import * as PlanBackend from "./backend/PlanBackend";
|
||||
@@ -36,7 +35,6 @@ class ProductBuyPage extends React.Component {
|
||||
pricing: props?.pricing ?? null,
|
||||
plan: null,
|
||||
isPlacingOrder: false,
|
||||
qrCodeModalProvider: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,13 +128,6 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
|
||||
buyProduct(product, provider) {
|
||||
if (provider.clientId.startsWith("http")) {
|
||||
this.setState({
|
||||
qrCodeModalProvider: provider,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isPlacingOrder: true,
|
||||
});
|
||||
@@ -144,7 +135,11 @@ class ProductBuyPage extends React.Component {
|
||||
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const payUrl = res.data;
|
||||
const payment = res.data;
|
||||
let payUrl = payment.payUrl;
|
||||
if (provider.type === "WeChat Pay") {
|
||||
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
|
||||
}
|
||||
Setting.goToLink(payUrl);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
@@ -159,45 +154,6 @@ class ProductBuyPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderQrCodeModal() {
|
||||
if (this.state.qrCodeModalProvider === undefined || this.state.qrCodeModalProvider === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title={
|
||||
<div>
|
||||
<CheckCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
||||
{" " + i18next.t("product:Please scan the QR code to pay")}
|
||||
</div>
|
||||
}
|
||||
open={this.state.qrCodeModalProvider !== undefined && this.state.qrCodeModalProvider !== null}
|
||||
onOk={() => {
|
||||
Setting.goToLink(this.state.product.returnUrl);
|
||||
}}
|
||||
onCancel={() => {
|
||||
this.setState({
|
||||
qrCodeModalProvider: null,
|
||||
});
|
||||
}}
|
||||
okText={i18next.t("product:I have completed the payment")}
|
||||
cancelText={i18next.t("general:Cancel")}>
|
||||
<p key={this.state.qrCodeModalProvider?.name}>
|
||||
{
|
||||
i18next.t("product:Please provide your username in the remark")
|
||||
}
|
||||
:
|
||||
{
|
||||
Setting.getTag("default", this.props.account.name)
|
||||
}
|
||||
<br />
|
||||
<br />
|
||||
<img src={this.state.qrCodeModalProvider?.clientId} alt={this.state.qrCodeModalProvider?.name} width={"472px"} style={{marginBottom: "20px"}} />
|
||||
</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
getPayButton(provider) {
|
||||
let text = provider.type;
|
||||
if (provider.type === "Dummy") {
|
||||
@@ -290,9 +246,6 @@ class ProductBuyPage extends React.Component {
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
{
|
||||
this.renderQrCodeModal()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@ import React from "react";
|
||||
import {Button, Card, Checkbox, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
@@ -24,7 +26,6 @@ import * as ProviderNotification from "./common/TestNotificationWidget";
|
||||
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
import * as Web3Auth from "./auth/Web3Auth";
|
||||
|
||||
@@ -39,6 +40,7 @@ class ProviderEditPage extends React.Component {
|
||||
providerName: props.match.params.providerName,
|
||||
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
provider: null,
|
||||
certs: [],
|
||||
organizations: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
@@ -47,6 +49,7 @@ class ProviderEditPage extends React.Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getOrganizations();
|
||||
this.getProvider();
|
||||
this.getCerts(this.state.owner);
|
||||
}
|
||||
|
||||
getProvider() {
|
||||
@@ -80,6 +83,17 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getCerts(owner) {
|
||||
CertBackend.getCerts(owner)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
certs: res.data || [],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parseProviderField(key, value) {
|
||||
if (["port"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
@@ -91,6 +105,11 @@ class ProviderEditPage extends React.Component {
|
||||
value = this.parseProviderField(key, value);
|
||||
|
||||
const provider = this.state.provider;
|
||||
if (key === "owner" && provider["owner"] !== value) {
|
||||
// the provider change the owner, reset the cert
|
||||
provider["cert"] = "";
|
||||
this.getCerts(value);
|
||||
}
|
||||
provider[key] = value;
|
||||
this.setState({
|
||||
provider: provider,
|
||||
@@ -182,6 +201,12 @@ class ProviderEditPage extends React.Component {
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
}
|
||||
case "Notification":
|
||||
if (provider.type === "Line") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
@@ -272,12 +297,15 @@ class ProviderEditPage extends React.Component {
|
||||
tooltip = i18next.t("provider:Project Id - Tooltip");
|
||||
}
|
||||
} else if (provider.category === "Email") {
|
||||
if (provider.type === "SUBMAIL") {
|
||||
if (provider.type === "SUBMAIL" || provider.type === "Azure ACS") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
}
|
||||
} else if (provider.category === "Notification") {
|
||||
if (provider.type === "Telegram") {
|
||||
if (provider.type === "Viber") {
|
||||
text = i18next.t("provider:Domain");
|
||||
tooltip = i18next.t("provider:Domain - Tooltip");
|
||||
} else if (provider.type === "Telegram" || provider.type === "DingTalk" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
|
||||
text = i18next.t("provider:App Key");
|
||||
tooltip = i18next.t("provider:App Key - Tooltip");
|
||||
}
|
||||
@@ -305,16 +333,25 @@ class ProviderEditPage extends React.Component {
|
||||
let text = "";
|
||||
let tooltip = "";
|
||||
|
||||
if (provider.type === "Telegram") {
|
||||
if (provider.type === "Telegram" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Twitter" || provider.type === "Reddit" || provider.type === "Rocket Chat" || provider.type === "Viber") {
|
||||
text = i18next.t("provider:Chat ID");
|
||||
tooltip = i18next.t("provider:Chat ID - Tooltip");
|
||||
} else if (provider.type === "Custom HTTP") {
|
||||
} else if (provider.type === "Custom HTTP" || provider.type === "Lark" || provider.type === "Microsoft Teams" || provider.type === "Webpush" || provider.type === "Matrix") {
|
||||
text = i18next.t("provider:Endpoint");
|
||||
tooltip = i18next.t("provider:Endpoint - Tooltip");
|
||||
} else if (provider.type === "DingTalk" || provider.type === "Bark") {
|
||||
text = i18next.t("provider:Secret Key");
|
||||
tooltip = i18next.t("provider:Secret Key - Tooltip");
|
||||
}
|
||||
|
||||
if (text === "" && tooltip === "") {
|
||||
return null;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("Test Notification", "Test Notification")} :
|
||||
</Col>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -406,7 +443,6 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("type", "Twilio SMS");
|
||||
} else if (value === "Storage") {
|
||||
this.updateProviderField("type", "AWS S3");
|
||||
this.updateProviderField("domain", Setting.getFullServerUrl());
|
||||
} else if (value === "SAML") {
|
||||
this.updateProviderField("type", "Keycloak");
|
||||
} else if (value === "Payment") {
|
||||
@@ -590,19 +626,25 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
{
|
||||
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
||||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
||||
(this.state.provider.category === "Web3") ||
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System" || (this.state.provider.category === "Notification")) ? null : (
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System" ||
|
||||
(this.state.provider.category === "Notification" && this.state.provider.type !== "Webpush" && this.state.provider.type !== "Line" && this.state.provider.type !== "Matrix" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "Rocket Chat" && this.state.provider.type !== "Viber")) ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel(this.state.provider)} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.type === "Line" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel(this.state.provider)} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecretLabel(this.state.provider)} :
|
||||
@@ -617,7 +659,7 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" ? null : (
|
||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@@ -630,7 +672,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.type === "WeChat Pay" ? null : (
|
||||
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecret2Label(this.state.provider)} :
|
||||
@@ -783,6 +825,18 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Google Chat"].includes(this.state.provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<TextArea rows={4} value={this.state.provider.metadata} onChange={e => {
|
||||
this.updateProviderField("metadata", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Content"), i18next.t("provider:Content - Tooltip"))} :
|
||||
@@ -813,26 +867,30 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.provider.port} onChange={value => {
|
||||
this.updateProviderField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.provider.disableSsl} onChange={checked => {
|
||||
this.updateProviderField("disableSsl", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.provider.port} onChange={value => {
|
||||
this.updateProviderField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.provider.disableSsl} onChange={checked => {
|
||||
this.updateProviderField("disableSsl", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
|
||||
@@ -862,9 +920,11 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
{i18next.t("provider:Test SMTP Connection")}
|
||||
</Button>
|
||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
{i18next.t("provider:Test SMTP Connection")}
|
||||
</Button>
|
||||
)}
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
|
||||
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
|
||||
@@ -909,15 +969,15 @@ class ProviderEditPage extends React.Component {
|
||||
<Col span={4} >
|
||||
<Input.Group compact>
|
||||
<CountryCodeSelect
|
||||
style={{width: "30%"}}
|
||||
value={this.state.provider.content}
|
||||
style={{width: "90px"}}
|
||||
initValue={this.state.provider.content}
|
||||
onChange={(value) => {
|
||||
this.updateProviderField("content", value);
|
||||
}}
|
||||
countryCodes={this.props.account.organization.countryCodes}
|
||||
/>
|
||||
<Input value={this.state.provider.receiver}
|
||||
style={{width: "70%"}}
|
||||
style={{width: "150px"}}
|
||||
placeholder = {i18next.t("user:Input your phone number")}
|
||||
onChange={e => {
|
||||
this.updateProviderField("receiver", e.target.value);
|
||||
@@ -1042,9 +1102,27 @@ class ProviderEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.cert} onChange={e => {
|
||||
this.updateProviderField("cert", e.target.value);
|
||||
}} />
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.cert} onChange={(value => {this.updateProviderField("cert", value);})}>
|
||||
{
|
||||
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
(this.state.provider.type === "Alipay") ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.metadata} onChange={(value => {this.updateProviderField("metadata", value);})}>
|
||||
{
|
||||
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
|
135
web/src/QrCodePage.js
Normal file
135
web/src/QrCodePage.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import QRCode from "qrcode.react";
|
||||
import {Button, Col, Row} from "antd";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class QrCodePage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.state = {
|
||||
classes: props,
|
||||
owner: props.owner ?? (props.match?.params?.owner ?? null),
|
||||
paymentName: props.paymentName ?? (props.match?.params?.paymentName ?? null),
|
||||
providerName: props.providerName ?? params.get("providerName"),
|
||||
payUrl: props.payUrl ?? params.get("payUrl"),
|
||||
successUrl: props.successUrl ?? params.get("successUrl"),
|
||||
provider: props.provider ?? null,
|
||||
payment: null,
|
||||
timer: null,
|
||||
};
|
||||
}
|
||||
|
||||
async getProvider() {
|
||||
if (!this.state.owner || !this.state.providerName) {
|
||||
return ;
|
||||
}
|
||||
try {
|
||||
const res = await ProviderBackend.getProvider(this.state.owner, this.state.providerName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const provider = res.data;
|
||||
this.setState({
|
||||
provider: provider,
|
||||
});
|
||||
} catch (err) {
|
||||
Setting.showMessage("error", err.message);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
setNotifyTask() {
|
||||
if (!this.state.owner || !this.state.paymentName) {
|
||||
return ;
|
||||
}
|
||||
|
||||
const notifyTask = async() => {
|
||||
try {
|
||||
const res = await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const payment = res.data;
|
||||
if (payment.state !== "Created") {
|
||||
Setting.goToLink(this.state.successUrl);
|
||||
}
|
||||
} catch (err) {
|
||||
Setting.showMessage("error", err.message);
|
||||
return ;
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({
|
||||
timer: setTimeout(async() => {
|
||||
await notifyTask();
|
||||
this.setNotifyTask();
|
||||
}, 2000),
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.onUpdateApplication) {
|
||||
this.props.onUpdateApplication(null);
|
||||
}
|
||||
this.getProvider();
|
||||
this.setNotifyTask();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.state.timer);
|
||||
}
|
||||
|
||||
renderProviderInfo(provider) {
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
const text = i18next.t(`product:${provider.type}`);
|
||||
return (
|
||||
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
||||
<img style={{marginRight: "10px"}} width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||
} size={"large"} >
|
||||
{
|
||||
text
|
||||
}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.payUrl || !this.state.successUrl || !this.state.owner || !this.state.paymentName) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="login-content">
|
||||
<Col>
|
||||
<Row style={{justifyContent: "center"}}>
|
||||
{this.renderProviderInfo(this.state.provider)}
|
||||
</Row>
|
||||
<Row style={{marginTop: "10px", justifyContent: "center"}}>
|
||||
<QRCode value={this.state.payUrl} size={this.props.size ?? 200} />
|
||||
</Row>
|
||||
</Col>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QrCodePage;
|
@@ -32,7 +32,8 @@ export const ServerUrl = "";
|
||||
|
||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
export const Countries = [
|
||||
{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
@@ -42,13 +43,19 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
||||
{label: "Português", key: "pt", country: "BR", alt: "Português"},
|
||||
{label: "Itariano", key: "it", country: "IT", alt: "Itariano"},
|
||||
{label: "Marley", key: "ms", country: "MY", alt: "Marley"},
|
||||
{label: "Tkiš", key: "tr", country: "TR", alt: "Tkiš"},
|
||||
{label: "لغة عربية", key: "ar", country: "DZ", alt: "لغة عربية"},
|
||||
{label: "Português", key: "pt", country: "PT", alt: "Português"},
|
||||
{label: "Italiano", key: "it", country: "IT", alt: "Italiano"},
|
||||
{label: "Malay", key: "ms", country: "MY", alt: "Malay"},
|
||||
{label: "Türkçe", key: "tr", country: "TR", alt: "Türkçe"},
|
||||
{label: "لغة عربية", key: "ar", country: "SA", alt: "لغة عربية"},
|
||||
{label: "עִבְרִית", key: "he", country: "IL", alt: "עִבְרִית"},
|
||||
{label: "Filipino", key: "fi", country: "PH", alt: "Filipino"},
|
||||
{label: "Nederlands", key: "nl", country: "NL", alt: "Nederlands"},
|
||||
{label: "Polski", key: "pl", country: "PL", alt: "Polski"},
|
||||
{label: "Suomi", key: "fi", country: "FI", alt: "Suomi"},
|
||||
{label: "Svenska", key: "sv", country: "SE", alt: "Svenska"},
|
||||
{label: "Українська", key: "uk", country: "UA", alt: "Українська"},
|
||||
{label: "Қазақ", key: "kk", country: "KZ", alt: "Қазақ"},
|
||||
{label: "فارسی", key: "fa", country: "IR", alt: "فارسی"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
@@ -154,6 +161,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/email_mailtrap.png`,
|
||||
url: "https://mailtrap.io",
|
||||
},
|
||||
"Azure ACS": {
|
||||
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
||||
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
||||
},
|
||||
},
|
||||
Storage: {
|
||||
"Local File System": {
|
||||
@@ -276,6 +287,70 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/email_default.png`,
|
||||
url: "https://casdoor.org/docs/provider/notification/overview",
|
||||
},
|
||||
"DingTalk": {
|
||||
logo: `${StaticBaseUrl}/img/social_dingtalk.png`,
|
||||
url: "https://www.dingtalk.com/",
|
||||
},
|
||||
"Lark": {
|
||||
logo: `${StaticBaseUrl}/img/social_lark.png`,
|
||||
url: "https://www.larksuite.com/",
|
||||
},
|
||||
"Microsoft Teams": {
|
||||
logo: `${StaticBaseUrl}/img/social_teams.png`,
|
||||
url: "https://www.microsoft.com/microsoft-teams",
|
||||
},
|
||||
"Bark": {
|
||||
logo: `${StaticBaseUrl}/img/social_bark.png`,
|
||||
url: "https://apps.apple.com/us/app/bark-customed-notifications/id1403753865",
|
||||
},
|
||||
"Pushover": {
|
||||
logo: `${StaticBaseUrl}/img/social_pushover.png`,
|
||||
url: "https://pushover.net/",
|
||||
},
|
||||
"Pushbullet": {
|
||||
logo: `${StaticBaseUrl}/img/social_pushbullet.png`,
|
||||
url: "https://www.pushbullet.com/",
|
||||
},
|
||||
"Slack": {
|
||||
logo: `${StaticBaseUrl}/img/social_slack.png`,
|
||||
url: "https://slack.com/",
|
||||
},
|
||||
"Webpush": {
|
||||
logo: `${StaticBaseUrl}/img/email_default.png`,
|
||||
url: "https://developer.mozilla.org/en-US/docs/Web/API/Push_API",
|
||||
},
|
||||
"Discord": {
|
||||
logo: `${StaticBaseUrl}/img/social_discord.png`,
|
||||
url: "https://discord.com/",
|
||||
},
|
||||
"Google Chat": {
|
||||
logo: `${StaticBaseUrl}/img/social_google_chat.png`,
|
||||
url: "https://workspace.google.com/intl/en/products/chat/",
|
||||
},
|
||||
"Line": {
|
||||
logo: `${StaticBaseUrl}/img/social_line.png`,
|
||||
url: "https://line.me/",
|
||||
},
|
||||
"Matrix": {
|
||||
logo: `${StaticBaseUrl}/img/social_matrix.png`,
|
||||
url: "https://www.matrix.org/",
|
||||
},
|
||||
"Twitter": {
|
||||
logo: `${StaticBaseUrl}/img/social_twitter.png`,
|
||||
url: "https://twitter.com/",
|
||||
},
|
||||
"Reddit": {
|
||||
logo: `${StaticBaseUrl}/img/social_reddit.png`,
|
||||
url: "https://www.reddit.com/",
|
||||
},
|
||||
"Rocket Chat": {
|
||||
logo: `${StaticBaseUrl}/img/social_rocket_chat.png`,
|
||||
url: "https://rocket.chat/",
|
||||
},
|
||||
"Viber": {
|
||||
logo: `${StaticBaseUrl}/img/social_viber.png`,
|
||||
url: "https://www.viber.com/",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -901,6 +976,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Default", name: "Default"},
|
||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||
{id: "Mailtrap", name: "Mailtrap"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
]
|
||||
);
|
||||
} else if (category === "SMS") {
|
||||
@@ -966,6 +1042,22 @@ export function getProviderTypeOptions(category) {
|
||||
return ([
|
||||
{id: "Telegram", name: "Telegram"},
|
||||
{id: "Custom HTTP", name: "Custom HTTP"},
|
||||
{id: "DingTalk", name: "DingTalk"},
|
||||
{id: "Lark", name: "Lark"},
|
||||
{id: "Microsoft Teams", name: "Microsoft Teams"},
|
||||
{id: "Bark", name: "Bark"},
|
||||
{id: "Pushover", name: "Pushover"},
|
||||
{id: "Pushbullet", name: "Pushbullet"},
|
||||
{id: "Slack", name: "Slack"},
|
||||
{id: "Webpush", name: "Webpush"},
|
||||
{id: "Discord", name: "Discord"},
|
||||
{id: "Google Chat", name: "Google Chat"},
|
||||
{id: "Line", name: "Line"},
|
||||
{id: "Matrix", name: "Matrix"},
|
||||
{id: "Twitter", name: "Twitter"},
|
||||
{id: "Reddit", name: "Reddit"},
|
||||
{id: "Rocket Chat", name: "Rocket Chat"},
|
||||
{id: "Viber", name: "Viber"},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
|
@@ -178,7 +178,7 @@ class SystemInfo extends React.Component {
|
||||
<Col span={6}></Col>
|
||||
</Row>
|
||||
<Tour
|
||||
open={this.state.isTourVisible}
|
||||
open={Setting.isMobile() ? false : this.state.isTourVisible}
|
||||
onClose={this.setIsTourVisible}
|
||||
steps={this.getSteps()}
|
||||
indicatorsRender={(current, total) => (
|
||||
|
@@ -402,7 +402,7 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<PasswordModal user={this.state.user} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
|
||||
<PasswordModal user={this.state.user} userName={this.state.userName} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@@ -379,7 +379,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
}
|
||||
|
||||
let endpoint = authInfo[provider.type].endpoint;
|
||||
const redirectUri = `${window.location.origin}/callback`;
|
||||
let redirectUri = `${window.location.origin}/callback`;
|
||||
const scope = authInfo[provider.type].scope;
|
||||
|
||||
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger");
|
||||
@@ -390,6 +390,8 @@ export function getAuthUrl(application, provider, method) {
|
||||
if (provider.domain !== "") {
|
||||
endpoint = endpoint.replace("common", provider.domain);
|
||||
}
|
||||
} else if (provider.type === "Apple") {
|
||||
redirectUri = `${window.location.origin}/api/callback`;
|
||||
}
|
||||
|
||||
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
|
||||
@@ -448,7 +450,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
} else if (provider.type === "Infoflow") {
|
||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`;
|
||||
} else if (provider.type === "Apple") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code%20id_token&scope=${scope}&response_mode=form_post`;
|
||||
} else if (provider.type === "Steam") {
|
||||
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
|
||||
} else if (provider.type === "Okta") {
|
||||
|
@@ -182,6 +182,8 @@ class SignupPage extends React.Component {
|
||||
AuthBackend.signup(values)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
// the user's id will be returned by `signup()`, if user signup by phone, the `username` in `values` is undefined.
|
||||
values.username = res.data.split("/")[1];
|
||||
if (Setting.hasPromptPage(application) && (!values.plan || !values.pricing)) {
|
||||
AuthBackend.getAccount("")
|
||||
.then((res) => {
|
||||
|
@@ -303,7 +303,7 @@ export function initWeb3Onboard(application, provider) {
|
||||
description: "Connect a wallet using Casdoor",
|
||||
recommendedInjectedWallets: [
|
||||
{name: "MetaMask", url: "https://metamask.io"},
|
||||
{name: "Coinbase", url: "https://wallet.coinbase.com/"},
|
||||
{name: "Coinbase", url: "https://www.coinbase.com/wallet"},
|
||||
],
|
||||
};
|
||||
|
||||
|
@@ -50,11 +50,15 @@ window.fetch = async(url, option = {}) => {
|
||||
requestFilters.forEach(filter => filter(url, option));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
originalFetch(url, option).then(res => {
|
||||
if (!url.startsWith("/api/get-organizations")) {
|
||||
responseFilters.forEach(filter => filter(res.clone()));
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
originalFetch(url, option)
|
||||
.then(res => {
|
||||
if (!url.startsWith("/api/get-organizations")) {
|
||||
responseFilters.forEach(filter => filter(res.clone()));
|
||||
}
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@@ -105,8 +105,8 @@ const Dashboard = (props) => {
|
||||
i18next.t("general:Applications"),
|
||||
i18next.t("general:Organizations"),
|
||||
i18next.t("general:Subscriptions"),
|
||||
]},
|
||||
grid: {left: "3%", right: "4%", bottom: "3%", containLabel: true},
|
||||
], top: "10%"},
|
||||
grid: {left: "3%", right: "4%", bottom: "0", top: "25%", containLabel: true},
|
||||
xAxis: {type: "category", boundaryGap: false, data: dateArray},
|
||||
yAxis: {type: "value"},
|
||||
series: [
|
||||
@@ -120,24 +120,24 @@ const Dashboard = (props) => {
|
||||
myChart.setOption(option);
|
||||
|
||||
return (
|
||||
<Row id="statistic" gutter={80}>
|
||||
<Col span={50}>
|
||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Row id="statistic" gutter={80} justify={"center"}>
|
||||
<Col span={50} style={{marginBottom: "10px"}}>
|
||||
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Statistic title={i18next.t("home:Total users")} fontSize="100px" value={dashboardData.userCounts[30]} valueStyle={{fontSize: "30px"}} style={{width: "200px", paddingLeft: "10px"}} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={50}>
|
||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Col span={50} style={{marginBottom: "10px"}}>
|
||||
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Statistic title={i18next.t("home:New users today")} fontSize="100px" value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 1]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={50}>
|
||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Col span={50} style={{marginBottom: "10px"}}>
|
||||
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Statistic title={i18next.t("home:New users past 7 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 7]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={50}>
|
||||
<Card bordered={false} bodyStyle={{width: "100%", height: "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Col span={50} style={{marginBottom: "10px"}}>
|
||||
<Card bordered={false} bodyStyle={{width: Setting.isMobile() ? "340px" : "100%", height: Setting.isMobile() ? "100px" : "150px", display: "flex", alignItems: "center", justifyContent: "center"}}>
|
||||
<Statistic title={i18next.t("home:New users past 30 days")} value={dashboardData.userCounts[30] - dashboardData.userCounts[30 - 30]} valueStyle={{fontSize: "30px"}} prefix={<ArrowUpOutlined />} style={{width: "200px", paddingLeft: "10px"}} />
|
||||
</Card>
|
||||
</Col>
|
||||
@@ -150,7 +150,7 @@ const Dashboard = (props) => {
|
||||
{renderEChart()}
|
||||
<div id="echarts-chart" style={{width: "80%", height: "400px", textAlign: "center", marginTop: "20px"}} />
|
||||
<Tour
|
||||
open={isTourVisible}
|
||||
open={Setting.isMobile() ? false : isTourVisible}
|
||||
onClose={setIsTourToLocal}
|
||||
steps={getSteps()}
|
||||
indicatorsRender={(current, total) => (
|
||||
|
@@ -49,7 +49,11 @@ export const AgreementModal = (props) => {
|
||||
function getTermsOfUseContent(url) {
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
}).then(r => r.text());
|
||||
})
|
||||
.then(r => r.text())
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to get TermsOfUse URL")}: ${url}, ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
export function isAgreementRequired(application) {
|
||||
|
@@ -26,6 +26,7 @@ export const PasswordModal = (props) => {
|
||||
const [newPassword, setNewPassword] = React.useState("");
|
||||
const [rePassword, setRePassword] = React.useState("");
|
||||
const {user} = props;
|
||||
const {userName} = props;
|
||||
const {organization} = props;
|
||||
const {account} = props;
|
||||
|
||||
@@ -90,7 +91,7 @@ export const PasswordModal = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
UserBackend.setPassword(user.owner, user.name, oldPassword, newPassword)
|
||||
UserBackend.setPassword(user.owner, userName, oldPassword, newPassword)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("user:Password set successfully"));
|
||||
|
@@ -29,7 +29,13 @@ import ms from "./locales/ms/data.json";
|
||||
import tr from "./locales/tr/data.json";
|
||||
import ar from "./locales/ar/data.json";
|
||||
import he from "./locales/he/data.json";
|
||||
import nl from "./locales/nl/data.json";
|
||||
import pl from "./locales/pl/data.json";
|
||||
import fi from "./locales/fi/data.json";
|
||||
import sv from "./locales/sv/data.json";
|
||||
import uk from "./locales/uk/data.json";
|
||||
import kk from "./locales/kk/data.json";
|
||||
import fa from "./locales/fa/data.json";
|
||||
import * as Conf from "./Conf";
|
||||
import {initReactI18next} from "react-i18next";
|
||||
|
||||
@@ -50,7 +56,13 @@ const resources = {
|
||||
tr: tr,
|
||||
ar: ar,
|
||||
he: he,
|
||||
nl: nl,
|
||||
pl: pl,
|
||||
fi: fi,
|
||||
sv: sv,
|
||||
uk: uk,
|
||||
kk: kk,
|
||||
fa: fa,
|
||||
};
|
||||
|
||||
function initLanguage() {
|
||||
@@ -115,9 +127,27 @@ function initLanguage() {
|
||||
case "he":
|
||||
language = "he";
|
||||
break;
|
||||
case "nl":
|
||||
language = "nl";
|
||||
break;
|
||||
case "pl":
|
||||
language = "pl";
|
||||
break;
|
||||
case "fi":
|
||||
language = "fi";
|
||||
break;
|
||||
case "sv":
|
||||
language = "sv";
|
||||
break;
|
||||
case "uk":
|
||||
language = "uk";
|
||||
break;
|
||||
case "kk":
|
||||
language = "kk";
|
||||
break;
|
||||
case "fa":
|
||||
language = "fa";
|
||||
break;
|
||||
default:
|
||||
language = Conf.DefaultLanguage;
|
||||
}
|
||||
|
1033
web/src/locales/fa/data.json
Normal file
1033
web/src/locales/fa/data.json
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user