mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-11 16:27:49 +08:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
66d0758b13 | ||
![]() |
46ad0fe0be | ||
![]() |
6b637e3b2e | ||
![]() |
3354945119 | ||
![]() |
19c4416f10 | ||
![]() |
2077db9091 | ||
![]() |
800f0ed249 | ||
![]() |
6161040c67 | ||
![]() |
1d785e61c6 | ||
![]() |
0329d24867 | ||
![]() |
fb6f3623ee | ||
![]() |
eb448bd043 | ||
![]() |
ea88839db9 | ||
![]() |
cb95f6977a | ||
![]() |
9067df92a7 |
@@ -16,6 +16,7 @@ ARG USER=casdoor
|
|||||||
|
|
||||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||||
RUN apk add --update sudo
|
RUN apk add --update sudo
|
||||||
|
RUN apk add tzdata
|
||||||
RUN apk add curl
|
RUN apk add curl
|
||||||
RUN apk add ca-certificates && update-ca-certificates
|
RUN apk add ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ socks5Proxy = "127.0.0.1:10808"
|
|||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 0
|
initScore = 0
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
|
isUsernameLowered = false
|
||||||
origin =
|
origin =
|
||||||
originFrontend =
|
originFrontend =
|
||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
|
@@ -261,16 +261,20 @@ func (c *ApiController) Signup() {
|
|||||||
c.SetSessionUsername(user.GetId())
|
c.SetSessionUsername(user.GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = object.DisableVerificationCode(authForm.Email)
|
if authForm.Email != "" {
|
||||||
if err != nil {
|
err = object.DisableVerificationCode(authForm.Email)
|
||||||
c.ResponseError(err.Error())
|
if err != nil {
|
||||||
return
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = object.DisableVerificationCode(checkPhone)
|
if checkPhone != "" {
|
||||||
if err != nil {
|
err = object.DisableVerificationCode(checkPhone)
|
||||||
c.ResponseError(err.Error())
|
if err != nil {
|
||||||
return
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
||||||
|
@@ -141,6 +141,20 @@ func (c *ApiController) GetProvider() {
|
|||||||
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
|
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) requireProviderPermission(provider *object.Provider) bool {
|
||||||
|
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||||
|
if isGlobalAdmin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.Owner == "admin" || user.Owner != provider.Owner {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateProvider
|
// UpdateProvider
|
||||||
// @Title UpdateProvider
|
// @Title UpdateProvider
|
||||||
// @Tag Provider API
|
// @Tag Provider API
|
||||||
@@ -159,6 +173,11 @@ func (c *ApiController) UpdateProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -184,11 +203,17 @@ func (c *ApiController) AddProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkQuotaForProvider(int(count)); err != nil {
|
err = checkQuotaForProvider(int(count))
|
||||||
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -208,6 +233,11 @@ func (c *ApiController) DeleteProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@@ -113,25 +113,25 @@ func (c *ApiController) SendEmail() {
|
|||||||
|
|
||||||
content := emailForm.Content
|
content := emailForm.Content
|
||||||
if content == "" {
|
if content == "" {
|
||||||
code := "123456"
|
content = provider.Content
|
||||||
|
}
|
||||||
|
|
||||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
code := "123456"
|
||||||
content = strings.Replace(provider.Content, "%s", code, 1)
|
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||||
if !strings.HasPrefix(userId, "app/") {
|
content = strings.Replace(content, "%s", code, 1)
|
||||||
var user *object.User
|
userString := "Hi"
|
||||||
user, err = object.GetUser(userId)
|
if !strings.HasPrefix(userId, "app/") {
|
||||||
if err != nil {
|
var user *object.User
|
||||||
c.ResponseError(err.Error())
|
user, err = object.GetUser(userId)
|
||||||
return
|
if err != nil {
|
||||||
}
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
userString := "Hi"
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
userString = user.GetFriendlyName()
|
userString = user.GetFriendlyName()
|
||||||
}
|
|
||||||
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
|
||||||
|
|
||||||
for _, receiver := range emailForm.Receivers {
|
for _, receiver := range emailForm.Receivers {
|
||||||
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
||||||
|
@@ -111,46 +111,44 @@ func newEmail(fromAddress string, toAddress string, subject string, content stri
|
|||||||
Subject: subject,
|
Subject: subject,
|
||||||
HTML: content,
|
HTML: content,
|
||||||
},
|
},
|
||||||
Importance: importanceNormal,
|
Importance: importanceNormal,
|
||||||
|
Attachments: []Attachment{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||||
postBody, err := json.Marshal(e)
|
email := newEmail(fromAddress, toAddress, subject, content)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("email JSON marshall failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyBuffer := bytes.NewBuffer(postBody)
|
postBody, err := json.Marshal(email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
endpoint := strings.TrimSuffix(a.Endpoint, "/")
|
endpoint := strings.TrimSuffix(a.Endpoint, "/")
|
||||||
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
|
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
|
||||||
|
|
||||||
|
bodyBuffer := bytes.NewBuffer(postBody)
|
||||||
req, err := http.NewRequest("POST", url, bodyBuffer)
|
req, err := http.NewRequest("POST", url, bodyBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating AzureACS API request: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign the request using the AzureACS access key and HMAC-SHA256
|
|
||||||
err = signRequestHMAC(a.AccessKey, req)
|
err = signRequestHMAC(a.AccessKey, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error signing AzureACS API request: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
// Some important header
|
|
||||||
req.Header.Set("repeatability-request-id", uuid.New().String())
|
req.Header.Set("repeatability-request-id", uuid.New().String())
|
||||||
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
|
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
// Send request
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error sending AzureACS API request: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Response error Handling
|
|
||||||
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
|
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
|
||||||
commError := ErrorResponse{}
|
commError := ErrorResponse{}
|
||||||
|
|
||||||
@@ -159,11 +157,11 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("error sending email: %s", commError.Error.Message)
|
return fmt.Errorf("status code: %d, error message: %s", resp.StatusCode, commError.Error.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusAccepted {
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
return fmt.Errorf("error sending email: status: %d", resp.StatusCode)
|
return fmt.Errorf("status code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -221,9 +219,3 @@ func GetHmac(content string, key []byte) string {
|
|||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
|
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)
|
|
||||||
}
|
|
||||||
|
@@ -23,6 +23,8 @@ func GetEmailProvider(typ string, clientId string, clientSecret string, host str
|
|||||||
return NewAzureACSEmailProvider(clientSecret, host)
|
return NewAzureACSEmailProvider(clientSecret, host)
|
||||||
} else if typ == "Custom HTTP Email" {
|
} else if typ == "Custom HTTP Email" {
|
||||||
return NewHttpEmailProvider(endpoint, method)
|
return NewHttpEmailProvider(endpoint, method)
|
||||||
|
} else if typ == "SendGrid" {
|
||||||
|
return NewSendgridEmailProvider(clientSecret)
|
||||||
} else {
|
} else {
|
||||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||||
}
|
}
|
||||||
|
68
email/sendgrid.go
Normal file
68
email/sendgrid.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2024 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sendgrid/sendgrid-go"
|
||||||
|
"github.com/sendgrid/sendgrid-go/helpers/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendgridEmailProvider struct {
|
||||||
|
ApiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendgridResponseBody struct {
|
||||||
|
Errors []struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Field interface{} `json:"field"`
|
||||||
|
Help interface{} `json:"help"`
|
||||||
|
} `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendgridEmailProvider(apiKey string) *SendgridEmailProvider {
|
||||||
|
return &SendgridEmailProvider{ApiKey: apiKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendgridEmailProvider) Send(fromAddress string, fromName, toAddress string, subject string, content string) error {
|
||||||
|
from := mail.NewEmail(fromName, fromAddress)
|
||||||
|
to := mail.NewEmail("", toAddress)
|
||||||
|
message := mail.NewSingleEmail(from, subject, to, "", content)
|
||||||
|
client := sendgrid.NewSendClient(s.ApiKey)
|
||||||
|
response, err := client.Send(message)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode >= 300 {
|
||||||
|
var responseBody SendgridResponseBody
|
||||||
|
err = json.Unmarshal([]byte(response.Body), &responseBody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
for _, sendgridError := range responseBody.Errors {
|
||||||
|
messages = append(messages, sendgridError.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("SendGrid status code: %d, error message: %s", response.StatusCode, strings.Join(messages, " | "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
5
go.mod
5
go.mod
@@ -19,7 +19,6 @@ require (
|
|||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||||
github.com/ethereum/go-ethereum v1.13.14
|
|
||||||
github.com/fogleman/gg v1.3.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/forestmgy/ldapserver v1.1.0
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5
|
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||||
@@ -40,12 +39,13 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/prometheus/client_golang v1.12.0
|
github.com/prometheus/client_golang v1.11.1
|
||||||
github.com/prometheus/client_model v0.4.0
|
github.com/prometheus/client_model v0.4.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.9.0
|
github.com/russellhaering/gosaml2 v0.9.0
|
||||||
github.com/russellhaering/goxmldsig v1.2.0
|
github.com/russellhaering/goxmldsig v1.2.0
|
||||||
|
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect
|
||||||
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||||
@@ -55,6 +55,7 @@ require (
|
|||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
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/builder v0.3.13
|
||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
|
@@ -15,43 +15,15 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EIP712Message struct {
|
|
||||||
Domain struct {
|
|
||||||
ChainId string `json:"chainId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
} `json:"domain"`
|
|
||||||
Message struct {
|
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
Nonce string `json:"nonce"`
|
|
||||||
CreateAt string `json:"createAt"`
|
|
||||||
} `json:"message"`
|
|
||||||
PrimaryType string `json:"primaryType"`
|
|
||||||
Types struct {
|
|
||||||
EIP712Domain []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
} `json:"EIP712Domain"`
|
|
||||||
AuthRequest []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
} `json:"AuthRequest"`
|
|
||||||
} `json:"types"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetaMaskIdProvider struct {
|
type MetaMaskIdProvider struct {
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
}
|
}
|
||||||
@@ -70,15 +42,6 @@ func (idp *MetaMaskIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
|
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
valid, err := VerifySignature(web3AuthToken.Address, web3AuthToken.TypedData, web3AuthToken.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, fmt.Errorf("invalid signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
token := &oauth2.Token{
|
token := &oauth2.Token{
|
||||||
AccessToken: web3AuthToken.Signature,
|
AccessToken: web3AuthToken.Signature,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
@@ -105,43 +68,3 @@ func (idp *MetaMaskIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
}
|
}
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifySignature(userAddress string, originalMessage string, signatureHex string) (bool, error) {
|
|
||||||
var eip712Mes EIP712Message
|
|
||||||
err := json.Unmarshal([]byte(originalMessage), &eip712Mes)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("invalid signature (Error parsing JSON)")
|
|
||||||
}
|
|
||||||
|
|
||||||
createAtTime, err := time.Parse("2006/1/2 15:04:05", eip712Mes.Message.CreateAt)
|
|
||||||
currentTime := time.Now()
|
|
||||||
if createAtTime.Before(currentTime.Add(-1*time.Minute)) && createAtTime.After(currentTime) {
|
|
||||||
return false, fmt.Errorf("invalid signature (signature does not meet time requirements)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(signatureHex, "0x") {
|
|
||||||
signatureHex = "0x" + signatureHex
|
|
||||||
}
|
|
||||||
|
|
||||||
signatureBytes, err := hex.DecodeString(signatureHex[2:])
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if signatureBytes[64] != 27 && signatureBytes[64] != 28 {
|
|
||||||
return false, fmt.Errorf("invalid signature (incorrect recovery id)")
|
|
||||||
}
|
|
||||||
signatureBytes[64] -= 27
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len([]byte(originalMessage)), []byte(originalMessage))
|
|
||||||
hash := crypto.Keccak256Hash([]byte(msg))
|
|
||||||
|
|
||||||
pubKey, err := crypto.SigToPub(hash.Bytes(), signatureBytes)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
|
|
||||||
|
|
||||||
return strings.EqualFold(recoveredAddr.Hex(), userAddress), nil
|
|
||||||
}
|
|
||||||
|
@@ -833,6 +833,11 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
|
||||||
|
if isUsernameLowered {
|
||||||
|
user.Name = strings.ToLower(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(user)
|
affected, err := ormer.Engine.Insert(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -846,6 +851,8 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
return false, fmt.Errorf("no users are provided")
|
return false, fmt.Errorf("no users are provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
|
||||||
|
|
||||||
// organization := GetOrganizationByUser(users[0])
|
// organization := GetOrganizationByUser(users[0])
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
||||||
@@ -869,6 +876,10 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isUsernameLowered {
|
||||||
|
user.Name = strings.ToLower(user.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(users)
|
affected, err := ormer.Engine.Insert(users)
|
||||||
|
@@ -3,14 +3,13 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/cssinjs": "1.16.1",
|
"@ant-design/cssinjs": "^1.10.1",
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@craco/craco": "^6.4.5",
|
"@craco/craco": "^6.4.5",
|
||||||
"@crowdin/cli": "^3.7.10",
|
"@crowdin/cli": "^3.7.10",
|
||||||
"@ctrl/tinycolor": "^3.5.0",
|
"@ctrl/tinycolor": "^3.5.0",
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@metamask/eth-sig-util": "^6.0.0",
|
"@metamask/eth-sig-util": "^6.0.0",
|
||||||
"@metamask/sdk-react": "^0.18.0",
|
|
||||||
"@web3-onboard/coinbase": "^2.2.5",
|
"@web3-onboard/coinbase": "^2.2.5",
|
||||||
"@web3-onboard/core": "^2.20.5",
|
"@web3-onboard/core": "^2.20.5",
|
||||||
"@web3-onboard/frontier": "^2.0.4",
|
"@web3-onboard/frontier": "^2.0.4",
|
||||||
|
@@ -374,6 +374,7 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onLoginSuccess={(redirectUrl) => {
|
onLoginSuccess={(redirectUrl) => {
|
||||||
|
window.google?.accounts?.id?.cancel();
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
||||||
}
|
}
|
||||||
|
@@ -244,7 +244,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
}
|
}
|
||||||
case "Email":
|
case "Email":
|
||||||
if (provider.type === "Azure ACS") {
|
if (provider.type === "Azure ACS" || provider.type === "SendGrid") {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
} else {
|
} else {
|
||||||
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
|
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
|
||||||
@@ -729,7 +729,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
(this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ||
|
||||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
@@ -770,7 +770,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ? null : (
|
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{this.getClientSecret2Label(this.state.provider)} :
|
{this.getClientSecret2Label(this.state.provider)} :
|
||||||
@@ -985,17 +985,19 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : this.state.provider.category === "Email" ? (
|
) : this.state.provider.category === "Email" ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
{["SendGrid"].includes(this.state.provider.type) ? null : (
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Row style={{marginTop: "20px"}} >
|
||||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
</Col>
|
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||||
<Col span={22} >
|
</Col>
|
||||||
<Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => {
|
<Col span={22} >
|
||||||
this.updateProviderField("host", e.target.value);
|
<Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => {
|
||||||
}} />
|
this.updateProviderField("host", e.target.value);
|
||||||
</Col>
|
}} />
|
||||||
</Row>
|
</Col>
|
||||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
</Row>
|
||||||
|
)}
|
||||||
|
{["Azure ACS", "SendGrid"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||||
@@ -1007,7 +1009,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
{["Azure ACS", "SendGrid"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Disable SSL"), i18next.t("provider:Disable SSL - Tooltip"))} :
|
||||||
@@ -1073,7 +1075,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("receiver", e.target.value);
|
this.updateProviderField("receiver", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
{["Azure ACS", "SendGrid"].includes(this.state.provider.type) ? null : (
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||||
{i18next.t("provider:Test SMTP Connection")}
|
{i18next.t("provider:Test SMTP Connection")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1337,20 +1339,6 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
|
||||||
this.state.provider.type === "MetaMask" ? (
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("provider:Signature messages"), i18next.t("provider:Signature messages - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<Input value={this.state.provider.metadata} onChange={e => {
|
|
||||||
this.updateProviderField("metadata", e.target.value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
|
||||||
|
@@ -181,6 +181,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
||||||
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
||||||
},
|
},
|
||||||
|
"SendGrid": {
|
||||||
|
logo: `${StaticBaseUrl}/img/email_sendgrid.png`,
|
||||||
|
url: "https://sendgrid.com/",
|
||||||
|
},
|
||||||
"Custom HTTP Email": {
|
"Custom HTTP Email": {
|
||||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
url: "https://casdoor.org/docs/provider/email/overview",
|
url: "https://casdoor.org/docs/provider/email/overview",
|
||||||
@@ -1015,6 +1019,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||||
{id: "Mailtrap", name: "Mailtrap"},
|
{id: "Mailtrap", name: "Mailtrap"},
|
||||||
{id: "Azure ACS", name: "Azure ACS"},
|
{id: "Azure ACS", name: "Azure ACS"},
|
||||||
|
{id: "SendGrid", name: "SendGrid"},
|
||||||
{id: "Custom HTTP Email", name: "Custom HTTP Email"},
|
{id: "Custom HTTP Email", name: "Custom HTTP Email"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Form, Input, InputNumber, List, Result, Row, Select, Space, Spin, Switch, Tag} from "antd";
|
import {Button, Card, Col, Form, Input, InputNumber, List, Result, Row, Select, Space, Spin, Switch, Tag, Tooltip} from "antd";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
import {TotpMfaType} from "./auth/MfaSetupPage";
|
import {TotpMfaType} from "./auth/MfaSetupPage";
|
||||||
import * as GroupBackend from "./backend/GroupBackend";
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
@@ -407,7 +407,17 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<PasswordModal user={this.state.user} userName={this.state.userName} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
|
{
|
||||||
|
(this.state.user.name === this.state.userName) ? (
|
||||||
|
<PasswordModal user={this.state.user} userName={this.state.userName} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
|
||||||
|
) : (
|
||||||
|
<Tooltip placement={"topLeft"} title={i18next.t("user:You have changed the username, please save your change first before modifying the password")}>
|
||||||
|
<span>
|
||||||
|
<PasswordModal user={this.state.user} userName={this.state.userName} organization={this.getUserOrganization()} account={this.props.account} disabled={true} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@@ -35,26 +35,48 @@ class VerificationListPage extends BaseListPage {
|
|||||||
renderTable(verifications) {
|
renderTable(verifications) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Name"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: "name",
|
dataIndex: "owner",
|
||||||
key: "name",
|
key: "owner",
|
||||||
width: "150px",
|
width: "120px",
|
||||||
fixed: "left",
|
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("name"),
|
...this.getColumnSearchProps("owner"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
if (text === "admin") {
|
||||||
|
return `(${i18next.t("general:empty")})`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/syncers/${text}`}>
|
<Link to={`/organizations/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "260px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "160px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Type"),
|
title: i18next.t("provider:Type"),
|
||||||
dataIndex: "type",
|
dataIndex: "type",
|
||||||
key: "type",
|
key: "type",
|
||||||
width: "120px",
|
width: "90px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("type"),
|
...this.getColumnSearchProps("type"),
|
||||||
},
|
},
|
||||||
@@ -67,7 +89,7 @@ class VerificationListPage extends BaseListPage {
|
|||||||
...this.getColumnSearchProps("user"),
|
...this.getColumnSearchProps("user"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/users/${record.owner}/${text}`}>
|
<Link to={`/users/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -88,6 +110,26 @@ class VerificationListPage extends BaseListPage {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Client IP"),
|
||||||
|
dataIndex: "remoteAddr",
|
||||||
|
key: "remoteAddr",
|
||||||
|
width: "100px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("remoteAddr"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
let clientIp = text;
|
||||||
|
if (clientIp.endsWith(": ")) {
|
||||||
|
clientIp = clientIp.slice(0, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${clientIp}`}>
|
||||||
|
{clientIp}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("verification:Receiver"),
|
title: i18next.t("verification:Receiver"),
|
||||||
dataIndex: "receiver",
|
dataIndex: "receiver",
|
||||||
@@ -100,30 +142,10 @@ class VerificationListPage extends BaseListPage {
|
|||||||
title: i18next.t("login:Verification code"),
|
title: i18next.t("login:Verification code"),
|
||||||
dataIndex: "code",
|
dataIndex: "code",
|
||||||
key: "code",
|
key: "code",
|
||||||
width: "120px",
|
width: "150px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("code"),
|
...this.getColumnSearchProps("code"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18next.t("general:Timestamp"),
|
|
||||||
dataIndex: "time",
|
|
||||||
key: "time",
|
|
||||||
width: "160px",
|
|
||||||
sorter: true,
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return Setting.getFormattedDate(text * 1000);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("general:Created time"),
|
|
||||||
dataIndex: "createdTime",
|
|
||||||
key: "createdTime",
|
|
||||||
width: "160px",
|
|
||||||
sorter: true,
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return Setting.getFormattedDate(text);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const paginationProps = {
|
const paginationProps = {
|
||||||
@@ -156,7 +178,7 @@ class VerificationListPage extends BaseListPage {
|
|||||||
value = params.type;
|
value = params.type;
|
||||||
}
|
}
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
VerificationBackend.getVerifications("admin", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
VerificationBackend.getVerifications("", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@@ -21,7 +21,7 @@ import * as Setting from "../Setting";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {SendCodeInput} from "../common/SendCodeInput";
|
import {SendCodeInput} from "../common/SendCodeInput";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
import {ArrowLeftOutlined, CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
import CustomGithubCorner from "../common/CustomGithubCorner";
|
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
import * as PasswordChecker from "../common/PasswordChecker";
|
import * as PasswordChecker from "../common/PasswordChecker";
|
||||||
@@ -443,6 +443,18 @@ class ForgetPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stepBack() {
|
||||||
|
if (this.state.current > 0) {
|
||||||
|
this.setState({
|
||||||
|
current: this.state.current - 1,
|
||||||
|
});
|
||||||
|
} else if (this.props.history.length > 1) {
|
||||||
|
this.props.history.goBack();
|
||||||
|
} else {
|
||||||
|
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application === undefined) {
|
if (application === undefined) {
|
||||||
@@ -456,6 +468,9 @@ class ForgetPage extends React.Component {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<CustomGithubCorner />
|
<CustomGithubCorner />
|
||||||
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
|
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
|
||||||
|
<Button type="text" style={{position: "relative", left: Setting.isMobile() ? "10px" : "-90px", top: 0}} size={"large"} onClick={() => {this.stepBack();}}>
|
||||||
|
<ArrowLeftOutlined style={{fontSize: "24px"}} />
|
||||||
|
</Button>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={24} style={{justifyContent: "center"}}>
|
<Col span={24} style={{justifyContent: "center"}}>
|
||||||
<Row>
|
<Row>
|
||||||
|
@@ -52,7 +52,7 @@ export function GoogleOneTapLoginVirtualButton(prop) {
|
|||||||
redirectUri = `${redirectUri}?state=${state}&code=${encodeURIComponent(code)}`;
|
redirectUri = `${redirectUri}?state=${state}&code=${encodeURIComponent(code)}`;
|
||||||
Setting.goToLink(redirectUri);
|
Setting.goToLink(redirectUri);
|
||||||
},
|
},
|
||||||
disableCancelOnUnmount: true,
|
disableCancelOnUnmount: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,107 +0,0 @@
|
|||||||
// Copyright 2024 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 {getAuthUrl} from "./Provider";
|
|
||||||
import {getProviderLogoURL, goToLink, showMessage} from "../Setting";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import {
|
|
||||||
generateNonce,
|
|
||||||
getWeb3AuthTokenKey,
|
|
||||||
setWeb3AuthToken
|
|
||||||
} from "./Web3Auth";
|
|
||||||
import {useSDK} from "@metamask/sdk-react";
|
|
||||||
import React, {useEffect} from "react";
|
|
||||||
|
|
||||||
export function MetaMaskLoginButton(props) {
|
|
||||||
const {application, web3Provider, method, width, margin} = props;
|
|
||||||
const {sdk, chainId, account} = useSDK();
|
|
||||||
const [typedData, setTypedData] = React.useState("");
|
|
||||||
const [nonce, setNonce] = React.useState("");
|
|
||||||
const [signature, setSignature] = React.useState();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (account && signature) {
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
const token = {
|
|
||||||
address: account,
|
|
||||||
nonce: nonce,
|
|
||||||
createAt: Math.floor(date.getTime() / 1000),
|
|
||||||
typedData: typedData,
|
|
||||||
signature: signature,
|
|
||||||
};
|
|
||||||
setWeb3AuthToken(token);
|
|
||||||
|
|
||||||
const redirectUri = `${getAuthUrl(application, web3Provider, method)}&web3AuthTokenKey=${getWeb3AuthTokenKey(account)}`;
|
|
||||||
goToLink(redirectUri);
|
|
||||||
}
|
|
||||||
}, [account, signature]);
|
|
||||||
|
|
||||||
const handleConnectAndSign = async() => {
|
|
||||||
try {
|
|
||||||
terminate();
|
|
||||||
|
|
||||||
const date = new Date();
|
|
||||||
|
|
||||||
const nonce = generateNonce();
|
|
||||||
setNonce(nonce);
|
|
||||||
|
|
||||||
const prompt = web3Provider?.metadata === "" ? "Casdoor: In order to authenticate to this website, sign this request and your public address will be sent to the server in a verifiable way." : web3Provider.metadata;
|
|
||||||
const typedData = JSON.stringify({
|
|
||||||
domain: {
|
|
||||||
chainId: chainId,
|
|
||||||
name: "Casdoor",
|
|
||||||
version: "1",
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
prompt: `${prompt}`,
|
|
||||||
nonce: nonce,
|
|
||||||
createAt: `${date.toLocaleString()}`,
|
|
||||||
},
|
|
||||||
primaryType: "AuthRequest",
|
|
||||||
types: {
|
|
||||||
EIP712Domain: [
|
|
||||||
{name: "name", type: "string"},
|
|
||||||
{name: "version", type: "string"},
|
|
||||||
{name: "chainId", type: "uint256"},
|
|
||||||
],
|
|
||||||
AuthRequest: [
|
|
||||||
{name: "prompt", type: "string"},
|
|
||||||
{name: "nonce", type: "string"},
|
|
||||||
{name: "createAt", type: "string"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setTypedData(typedData);
|
|
||||||
|
|
||||||
const sig = await sdk.connectAndSign({msg: typedData});
|
|
||||||
setSignature(sig);
|
|
||||||
} catch (err) {
|
|
||||||
showMessage("error", `${i18next.t("login:Failed to obtain MetaMask authorization")}: ${err.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminate = () => {
|
|
||||||
sdk?.terminate();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a key={web3Provider.displayName} onClick={handleConnectAndSign}>
|
|
||||||
<img width={width} height={width} src={getProviderLogoURL(web3Provider)} alt={web3Provider.displayName}
|
|
||||||
className="provider-img" style={{margin: margin}} />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MetaMaskLoginButton;
|
|
@@ -43,8 +43,6 @@ import DouyinLoginButton from "./DouyinLoginButton";
|
|||||||
import LoginButton from "./LoginButton";
|
import LoginButton from "./LoginButton";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import {WechatOfficialAccountModal} from "./Util";
|
import {WechatOfficialAccountModal} from "./Util";
|
||||||
import {MetaMaskProvider} from "@metamask/sdk-react";
|
|
||||||
import MetaMaskLoginButton from "./MetaMaskLoginButton";
|
|
||||||
|
|
||||||
function getSigninButton(provider) {
|
function getSigninButton(provider) {
|
||||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName !== "" ? provider.displayName : provider.type);
|
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName !== "" ? provider.displayName : provider.type);
|
||||||
@@ -162,36 +160,11 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else if (provider.category === "Web3") {
|
} else if (provider.category === "Web3") {
|
||||||
if (provider.type === "MetaMask") {
|
return (
|
||||||
return (
|
<a key={provider.displayName} onClick={() => goToWeb3Url(application, provider, "signup")}>
|
||||||
<MetaMaskProvider
|
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} className="provider-img" style={{margin: margin}} />
|
||||||
debug={false}
|
</a>
|
||||||
sdkOptions={{
|
);
|
||||||
communicationServerUrl: process.env.REACT_APP_COMM_SERVER_URL,
|
|
||||||
checkInstallationImmediately: false, // This will automatically connect to MetaMask on page load
|
|
||||||
dappMetadata: {
|
|
||||||
name: "Casdoor",
|
|
||||||
url: window.location.protocol + "//" + window.location.host,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MetaMaskLoginButton
|
|
||||||
application={application}
|
|
||||||
web3Provider={provider}
|
|
||||||
method={"signup"}
|
|
||||||
width={width}
|
|
||||||
margin={margin}
|
|
||||||
/>
|
|
||||||
</MetaMaskProvider>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<a key={provider.displayName} onClick={() => goToWeb3Url(application, provider, "signup")}>
|
|
||||||
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName}
|
|
||||||
className="provider-img" style={{margin: margin}} />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
// style definition
|
// style definition
|
||||||
|
1098
web/yarn.lock
1098
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user