forked from casdoor/casdoor
Compare commits
14 Commits
copilot/fi
...
v2.333.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c9952e3d9 | ||
|
|
bbaa28133f | ||
|
|
baef7680ea | ||
|
|
d15b66177c | ||
|
|
5ce6bac529 | ||
|
|
0621f35665 | ||
|
|
1ac2490419 | ||
|
|
8c50ada494 | ||
|
|
22da90576e | ||
|
|
b00404cb3a | ||
|
|
2ed27f4f0a | ||
|
|
bf538d5260 | ||
|
|
13ee5fd150 | ||
|
|
04cdd5a012 |
@@ -185,10 +185,14 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
} else {
|
||||
scope := c.Ctx.Input.Query("scope")
|
||||
nonce := c.Ctx.Input.Query("nonce")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
if !object.IsScopeValid(scope, application) {
|
||||
resp = &Response{Status: "error", Msg: "error: invalid_scope", Data: ""}
|
||||
} else {
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
|
||||
resp.Data3 = user.NeedUpdatePassword
|
||||
resp.Data3 = user.NeedUpdatePassword
|
||||
}
|
||||
}
|
||||
} else if form.Type == ResponseTypeDevice {
|
||||
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
|
||||
@@ -954,11 +958,13 @@ func (c *ApiController) Login() {
|
||||
RegisterSource: fmt.Sprintf("%s/%s", application.Organization, application.Name),
|
||||
}
|
||||
|
||||
// Set group from invitation code if available, otherwise use provider's signup group
|
||||
// Set group from invitation code if available, otherwise use provider's signup group or application's default group
|
||||
if invitation != nil && invitation.SignupGroup != "" {
|
||||
user.Groups = []string{invitation.SignupGroup}
|
||||
} else if providerItem.SignupGroup != "" {
|
||||
user.Groups = []string{providerItem.SignupGroup}
|
||||
} else if application.DefaultGroup != "" {
|
||||
user.Groups = []string{application.DefaultGroup}
|
||||
}
|
||||
|
||||
var affected bool
|
||||
|
||||
@@ -162,6 +162,9 @@ func (c *ApiController) DeleteToken() {
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
clientId := c.Ctx.Input.Query("client_id")
|
||||
clientSecret := c.Ctx.Input.Query("client_secret")
|
||||
assertion := c.Ctx.Input.Query("assertion")
|
||||
clientAssertion := c.Ctx.Input.Query("client_assertion")
|
||||
clientAssertionType := c.Ctx.Input.Query("client_assertion_type")
|
||||
grantType := c.Ctx.Input.Query("grant_type")
|
||||
code := c.Ctx.Input.Query("code")
|
||||
verifier := c.Ctx.Input.Query("code_verifier")
|
||||
@@ -193,6 +196,12 @@ func (c *ApiController) GetOAuthToken() {
|
||||
if clientSecret == "" {
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
}
|
||||
if clientAssertion == "" {
|
||||
clientAssertion = tokenRequest.ClientAssertion
|
||||
}
|
||||
if clientAssertionType == "" {
|
||||
clientAssertionType = tokenRequest.ClientAssertionType
|
||||
}
|
||||
if grantType == "" {
|
||||
grantType = tokenRequest.GrantType
|
||||
}
|
||||
@@ -235,9 +244,13 @@ func (c *ApiController) GetOAuthToken() {
|
||||
if resource == "" {
|
||||
resource = tokenRequest.Resource
|
||||
}
|
||||
if assertion == "" {
|
||||
assertion = tokenRequest.Assertion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
host := c.Ctx.Request.Host
|
||||
if deviceCode != "" {
|
||||
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||
if !ok {
|
||||
@@ -278,8 +291,7 @@ func (c *ApiController) GetOAuthToken() {
|
||||
username = deviceAuthCacheCast.UserName
|
||||
}
|
||||
|
||||
host := c.Ctx.Request.Host
|
||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, audience, resource)
|
||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, assertion, clientAssertion, clientAssertionType, audience, resource)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -323,7 +335,12 @@ func (c *ApiController) RefreshToken() {
|
||||
}
|
||||
}
|
||||
|
||||
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
ok, application, clientId, _, err := c.ValidateOAuth(true)
|
||||
if err != nil || !ok {
|
||||
return
|
||||
}
|
||||
|
||||
refreshToken2, err := object.RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -334,14 +351,79 @@ func (c *ApiController) RefreshToken() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ResponseTokenError(errorMsg string) {
|
||||
func (c *ApiController) ResponseTokenError(errorMsg string, errorDescription string) {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: errorMsg,
|
||||
Error: errorMsg,
|
||||
ErrorDescription: errorDescription,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ValidateOAuth(ignoreValidSecret bool) (ok bool, application *object.Application, clientId, clientSecret string, err error) {
|
||||
reqClientId := c.Ctx.Input.Query("client_id")
|
||||
reqClientSecret := c.Ctx.Input.Query("client_secret")
|
||||
clientAssertion := c.Ctx.Input.Query("client_assertion")
|
||||
clientAssertionType := c.Ctx.Input.Query("client_assertion_type")
|
||||
|
||||
if reqClientId == "" && clientAssertionType == "" {
|
||||
var tokenRequest TokenRequest
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||
reqClientId = tokenRequest.ClientId
|
||||
reqClientSecret = tokenRequest.ClientSecret
|
||||
clientAssertion = tokenRequest.ClientAssertion
|
||||
clientAssertionType = tokenRequest.ClientAssertionType
|
||||
}
|
||||
}
|
||||
|
||||
if clientAssertionType == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
|
||||
ok, application, err = object.ValidateClientAssertion(clientAssertion, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !ok || application == nil {
|
||||
c.ResponseTokenError(object.InvalidClient, "client_assertion is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
clientSecret = application.ClientSecret
|
||||
clientId = application.ClientId
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
if reqClientId == "" && reqClientSecret == "" {
|
||||
clientId, clientSecret, ok = c.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
clientId = c.Ctx.Input.Query("client_id")
|
||||
clientSecret = c.Ctx.Input.Query("client_secret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
c.ResponseTokenError(object.InvalidRequest, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clientId = reqClientId
|
||||
clientSecret = reqClientSecret
|
||||
}
|
||||
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil || (application.ClientSecret != clientSecret && !ignoreValidSecret) {
|
||||
c.ResponseTokenError(object.InvalidClient, c.T("token:Invalid application or wrong clientSecret"))
|
||||
return
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// IntrospectToken
|
||||
// @Title IntrospectToken
|
||||
// @Tag Login API
|
||||
@@ -349,7 +431,7 @@ func (c *ApiController) ResponseTokenError(errorMsg string) {
|
||||
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||
// representing the meta information surrounding the
|
||||
// token, including whether this token is currently active.
|
||||
// This endpoint only support Basic Authorization.
|
||||
// This endpoint support Basic Authorization and authorization defined in RFC 7523.
|
||||
//
|
||||
// @Param token formData string true "access_token's value or refresh_token's value"
|
||||
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
|
||||
@@ -359,24 +441,9 @@ func (c *ApiController) ResponseTokenError(errorMsg string) {
|
||||
// @router /login/oauth/introspect [post]
|
||||
func (c *ApiController) IntrospectToken() {
|
||||
tokenValue := c.Ctx.Input.Query("token")
|
||||
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
clientId = c.Ctx.Input.Query("client_id")
|
||||
clientSecret = c.Ctx.Input.Query("client_secret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
c.ResponseTokenError(object.InvalidRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
c.ResponseTokenError(c.T("token:Invalid application or wrong clientSecret"))
|
||||
ok, application, clientId, _, err := c.ValidateOAuth(false)
|
||||
if err != nil || !ok {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -390,7 +457,7 @@ func (c *ApiController) IntrospectToken() {
|
||||
if tokenTypeHint != "" {
|
||||
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(err.Error())
|
||||
c.ResponseTokenError(object.InvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil || token.ExpiresIn <= 0 {
|
||||
@@ -467,7 +534,7 @@ func (c *ApiController) IntrospectToken() {
|
||||
if tokenTypeHint == "" {
|
||||
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(err.Error())
|
||||
c.ResponseTokenError(object.InvalidRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil || token.ExpiresIn <= 0 {
|
||||
@@ -479,7 +546,7 @@ func (c *ApiController) IntrospectToken() {
|
||||
if token != nil {
|
||||
application, err = object.GetApplication(fmt.Sprintf("%s/%s", token.Owner, token.Application))
|
||||
if err != nil {
|
||||
c.ResponseTokenError(err.Error())
|
||||
c.ResponseTokenError(object.InvalidClient, err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
|
||||
@@ -15,20 +15,23 @@
|
||||
package controllers
|
||||
|
||||
type TokenRequest struct {
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
Verifier string `json:"code_verifier"`
|
||||
Scope string `json:"scope"`
|
||||
Nonce string `json:"nonce"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Tag string `json:"tag"`
|
||||
Avatar string `json:"avatar"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
SubjectToken string `json:"subject_token"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
Audience string `json:"audience"`
|
||||
Resource string `json:"resource"` // RFC 8707 Resource Indicator
|
||||
Assertion string `json:"assertion"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
ClientAssertion string `json:"client_assertion"`
|
||||
ClientAssertionType string `json:"client_assertion_type"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
Verifier string `json:"code_verifier"`
|
||||
Scope string `json:"scope"`
|
||||
Nonce string `json:"nonce"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Tag string `json:"tag"`
|
||||
Avatar string `json:"avatar"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
SubjectToken string `json:"subject_token"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
Audience string `json:"audience"`
|
||||
Resource string `json:"resource"` // RFC 8707 Resource Indicator
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
{"name": "ID", "visible": true, "viewRule": "Public", "modifyRule": "Immutable"},
|
||||
{"name": "Name", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
{"name": "Display name", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "First name", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "Last name", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "Avatar", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "User type", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
{"name": "Password", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
@@ -81,6 +83,7 @@
|
||||
{"name": "Title", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "ID card type", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "ID card", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "ID card info", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "Real name", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
{"name": "ID verification", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "Homepage", "visible": true, "viewRule": "Public", "modifyRule": "Self"},
|
||||
@@ -101,6 +104,7 @@
|
||||
{"name": "Signup application", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
{"name": "Register type", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
{"name": "Register source", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
{"name": "API key", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "Roles", "visible": true, "viewRule": "Public", "modifyRule": "Immutable"},
|
||||
{"name": "Permissions", "visible": true, "viewRule": "Public", "modifyRule": "Immutable"},
|
||||
{"name": "Groups", "visible": true, "viewRule": "Public", "modifyRule": "Admin"},
|
||||
@@ -110,9 +114,14 @@
|
||||
{"name": "Is forbidden", "visible": true, "viewRule": "Admin", "modifyRule": "Admin"},
|
||||
{"name": "Is deleted", "visible": true, "viewRule": "Admin", "modifyRule": "Admin"},
|
||||
{"name": "Multi-factor authentication", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "MFA items", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "WebAuthn credentials", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "Last change password time", "visible": true, "viewRule": "Admin", "modifyRule": "Admin"},
|
||||
{"name": "Managed accounts", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "MFA accounts", "visible": true, "viewRule": "Self", "modifyRule": "Self"}
|
||||
{"name": "Face ID", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "MFA accounts", "visible": true, "viewRule": "Self", "modifyRule": "Self"},
|
||||
{"name": "Need update password", "visible": true, "viewRule": "Admin", "modifyRule": "Admin"},
|
||||
{"name": "IP whitelist", "visible": true, "viewRule": "Admin", "modifyRule": "Admin"}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -125,6 +125,7 @@ type Application struct {
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
ClientCert string `xorm:"varchar(100)" json:"clientCert"`
|
||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||
ForcedRedirectOrigin string `xorm:"varchar(100)" json:"forcedRedirectOrigin"`
|
||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||
|
||||
@@ -53,6 +53,8 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "First name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Last name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
@@ -67,6 +69,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "ID card type", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "ID card", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "ID card info", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Real name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
{Name: "ID verification", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
|
||||
@@ -87,6 +90,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Register type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Register source", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "API key", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Groups", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
@@ -96,9 +100,14 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "MFA items", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Last change password time", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Face ID", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Need update password", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "IP whitelist", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -660,6 +660,15 @@ func generateJwtToken(application *Application, user *User, provider string, sig
|
||||
return tokenString, refreshTokenString, name, err
|
||||
}
|
||||
|
||||
func ParseJwtTokenWithoutValidation(token string) (*jwt.Token, error) {
|
||||
t, _, err := jwt.NewParser().ParseUnverified(token, &Claims{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
var (
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -154,6 +155,10 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
||||
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application, nil
|
||||
}
|
||||
|
||||
if !IsScopeValid(scope, application) {
|
||||
return i18n.Translate(lang, "token:Invalid scope"), application, nil
|
||||
}
|
||||
|
||||
// Mask application for /api/get-app-login
|
||||
application.ClientSecret = ""
|
||||
return "", application, nil
|
||||
@@ -240,10 +245,33 @@ func GetOAuthCode(userId string, clientId string, provider string, signinMethod
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, audience string, resource string) (interface{}, error) {
|
||||
application, err := GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, assertion string, clientAssertion string, clientAssertionType string, audience string, resource string) (interface{}, error) {
|
||||
var (
|
||||
application *Application
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
|
||||
if clientAssertionType == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" {
|
||||
ok, application, err = ValidateClientAssertion(clientAssertion, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok || application == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidClient,
|
||||
ErrorDescription: "client_assertion is invalid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
clientSecret = application.ClientSecret
|
||||
clientId = application.ClientId
|
||||
} else {
|
||||
application, err = GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
@@ -273,12 +301,14 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||
case "token", "id_token": // Implicit Grant
|
||||
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||
case "urn:ietf:params:oauth:grant-type:jwt-bearer":
|
||||
token, tokenError, err = GetJwtBearerToken(application, assertion, scope, nonce, host)
|
||||
case "urn:ietf:params:oauth:grant-type:device_code":
|
||||
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||
case "urn:ietf:params:oauth:grant-type:token-exchange": // Token Exchange Grant (RFC 8693)
|
||||
token, tokenError, err = GetTokenExchangeToken(application, clientSecret, subjectToken, subjectTokenType, audience, scope, host)
|
||||
case "refresh_token":
|
||||
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
refreshToken2, err := RefreshToken(application, grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -320,7 +350,7 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
return tokenWrapper, nil
|
||||
}
|
||||
|
||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
|
||||
func RefreshToken(application *Application, grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
|
||||
// check parameters
|
||||
if grantType != "refresh_token" {
|
||||
return &TokenError{
|
||||
@@ -328,16 +358,20 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ErrorDescription: "grant_type should be refresh_token",
|
||||
}, nil
|
||||
}
|
||||
application, err := GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
if application == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidClient,
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}, nil
|
||||
application, err = GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidClient,
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||
@@ -486,6 +520,28 @@ func IsGrantTypeValid(method string, grantTypes []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsScopeValid checks whether all space-separated scopes in the scope string
|
||||
// are defined in the application's Scopes list.
|
||||
// If the application has no defined scopes, every scope is considered valid
|
||||
// (backward-compatible behaviour).
|
||||
func IsScopeValid(scope string, application *Application) bool {
|
||||
if len(application.Scopes) == 0 || scope == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
allowed := make(map[string]bool, len(application.Scopes))
|
||||
for _, s := range application.Scopes {
|
||||
allowed[s.Name] = true
|
||||
}
|
||||
|
||||
for _, s := range strings.Fields(scope) {
|
||||
if !allowed[s] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// createGuestUserToken creates a new guest user and returns a token for them
|
||||
func createGuestUserToken(application *Application, clientSecret string, verifier string) (*Token, *TokenError, error) {
|
||||
// Verify client secret if provided
|
||||
@@ -715,6 +771,13 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
||||
// GetPasswordToken
|
||||
// Resource Owner Password Credentials flow
|
||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
|
||||
if !IsScopeValid(scope, application) {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidScope,
|
||||
ErrorDescription: "the requested scope is invalid or not defined in the application",
|
||||
}, nil
|
||||
}
|
||||
|
||||
user, err := GetUserByFields(application.Organization, username)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -796,6 +859,12 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}, nil
|
||||
}
|
||||
if !IsScopeValid(scope, application) {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidScope,
|
||||
ErrorDescription: "the requested scope is invalid or not defined in the application",
|
||||
}, nil
|
||||
}
|
||||
nullUser := &User{
|
||||
Owner: application.Owner,
|
||||
Id: application.GetId(),
|
||||
@@ -835,6 +904,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
// GetImplicitToken
|
||||
// Implicit flow
|
||||
func GetImplicitToken(application *Application, username string, scope string, nonce string, host string) (*Token, *TokenError, error) {
|
||||
if !IsScopeValid(scope, application) {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidScope,
|
||||
ErrorDescription: "the requested scope is invalid or not defined in the application",
|
||||
}, nil
|
||||
}
|
||||
|
||||
user, err := GetUserByFields(application.Organization, username)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -859,6 +935,84 @@ func GetImplicitToken(application *Application, username string, scope string, n
|
||||
return token, nil, nil
|
||||
}
|
||||
|
||||
// GetJwtBearerToken
|
||||
// RFC 7523
|
||||
func GetJwtBearerToken(application *Application, assertion string, scope string, nonce string, host string) (*Token, *TokenError, error) {
|
||||
ok, claims, err := ValidateJwtAssertion(assertion, application, host)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: err.Error(),
|
||||
}, err
|
||||
}
|
||||
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: fmt.Sprintf("assertion (JWT) is invalid for application: [%s]", application.GetId()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return GetImplicitToken(application, claims.Subject, scope, nonce, host)
|
||||
}
|
||||
|
||||
func ValidateJwtAssertion(clientAssertion string, application *Application, host string) (bool, *Claims, error) {
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
clientCert, err := getCert(application.Owner, application.ClientCert)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if clientCert == nil {
|
||||
return false, nil, fmt.Errorf("client certificate is not configured for application: [%s]", application.GetId())
|
||||
}
|
||||
|
||||
claims, err := ParseJwtToken(clientAssertion, clientCert)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if !slices.Contains(application.RedirectUris, claims.Issuer) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
if !slices.Contains(claims.Audience, fmt.Sprintf("%s/api/login/oauth/access_token", originBackend)) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
return true, claims, nil
|
||||
}
|
||||
|
||||
func ValidateClientAssertion(clientAssertion string, host string) (bool, *Application, error) {
|
||||
token, err := ParseJwtTokenWithoutValidation(clientAssertion)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
clientId, err := token.Claims.GetSubject()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
application, err := GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if application == nil {
|
||||
return false, nil, fmt.Errorf("application not found for client: [%s]", clientId)
|
||||
}
|
||||
|
||||
ok, _, err := ValidateJwtAssertion(clientAssertion, application, host)
|
||||
if err != nil {
|
||||
return false, application, err
|
||||
}
|
||||
if !ok {
|
||||
return false, application, nil
|
||||
}
|
||||
|
||||
return true, application, nil
|
||||
}
|
||||
|
||||
// GetTokenByUser
|
||||
// Implicit flow
|
||||
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
||||
|
||||
@@ -180,6 +180,7 @@ type User struct {
|
||||
Spotify string `xorm:"spotify varchar(100)" json:"spotify"`
|
||||
Strava string `xorm:"strava varchar(100)" json:"strava"`
|
||||
Stripe string `xorm:"stripe varchar(100)" json:"stripe"`
|
||||
Telegram string `xorm:"telegram varchar(100)" json:"telegram"`
|
||||
TikTok string `xorm:"tiktok varchar(100)" json:"tiktok"`
|
||||
Tumblr string `xorm:"tumblr varchar(100)" json:"tumblr"`
|
||||
Twitch string `xorm:"twitch varchar(100)" json:"twitch"`
|
||||
@@ -860,16 +861,16 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "email_verified",
|
||||
"location", "address", "addresses", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application", "register_type", "register_source",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "mfa_items", "last_change_password_time", "managedAccounts", "face_ids", "mfaAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "email_verified",
|
||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
|
||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "mfa_items", "mfa_remember_deadline",
|
||||
"spotify", "strava", "stripe", "type", "telegram", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "mfa_remember_deadline",
|
||||
"cart",
|
||||
}
|
||||
}
|
||||
@@ -954,6 +955,13 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
|
||||
user.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
if len(user.Groups) > 0 {
|
||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -506,157 +506,6 @@ class ApplicationEditPage extends React.Component {
|
||||
)}
|
||||
{this.state.activeMenuKey === "authentication" && (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={this.state.application.clientId} onChange={e => {
|
||||
this.updateApplicationField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={this.state.application.clientSecret} onChange={e => {
|
||||
this.updateApplicationField("clientSecret", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<UrlTable
|
||||
title={i18next.t("application:Redirect URLs")}
|
||||
table={this.state.application.redirectUris}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("redirectUris", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Forced redirect origin"), i18next.t("general:Forced redirect origin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.forcedRedirectOrigin} onChange={e => {
|
||||
this.updateApplicationField("forcedRedirectOrigin", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}}
|
||||
value={this.state.application.grantTypes}
|
||||
onChange={(value => {
|
||||
this.updateApplicationField("grantTypes", value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{id: "authorization_code", name: "Authorization Code"},
|
||||
{id: "password", name: "Password"},
|
||||
{id: "client_credentials", name: "Client Credentials"},
|
||||
{id: "token", name: "Token"},
|
||||
{id: "id_token", name: "ID Token"},
|
||||
{id: "refresh_token", name: "Refresh Token"},
|
||||
{id: "urn:ietf:params:oauth:grant-type:device_code", name: "Device Code"},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(this.state.application.category === "Agent") ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("general:Scopes"), i18next.t("general:Scopes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<ScopeTable
|
||||
title={i18next.t("general:Scopes")}
|
||||
table={this.state.application.scopes}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("scopes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
||||
options={["JWT", "JWT-Empty", "JWT-Custom", "JWT-Standard"].map((item) => Setting.getOption(item, item))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenSigningMethod === "" ? "RS256" : this.state.application.tokenSigningMethod} onChange={(value => {this.updateApplicationField("tokenSigningMethod", value);})}
|
||||
options={["RS256", "RS512", "ES256", "ES512", "ES384"].map((item) => Setting.getOption(item, item))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} disabled={this.state.application.tokenFormat !== "JWT-Custom"} mode="tags" showSearch style={{width: "100%"}} value={this.state.application.tokenFields} onChange={(value => {this.updateApplicationField("tokenFields", value);})}>
|
||||
<Option key={"signinMethod"} value={"signinMethod"}>{"SigninMethod"}</Option>
|
||||
<Option key={"provider"} value={"provider"}>{"Provider"}</Option>
|
||||
{
|
||||
[...Setting.getUserCommonFields(), "permissionNames"].map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.application.tokenFormat === "JWT-Custom" ? (<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Token attributes"), i18next.t("general:Token attributes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<TokenAttributeTable
|
||||
title={i18next.t("general:Token attributes")}
|
||||
table={this.state.application.tokenAttributes}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("tokenAttributes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.expireInHours} min={0.01} step={1} precision={2} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("expireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.refreshExpireInHours} min={0.01} step={1} precision={2} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("refreshExpireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Cookie expire"), i18next.t("application:Cookie expire - Tooltip"))} :
|
||||
@@ -807,7 +656,167 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{this.state.activeMenuKey === "oidc-oauth" && (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={this.state.application.clientId} onChange={e => {
|
||||
this.updateApplicationField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={this.state.application.clientSecret} onChange={e => {
|
||||
this.updateApplicationField("clientSecret", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<UrlTable
|
||||
title={i18next.t("application:Redirect URLs")}
|
||||
table={this.state.application.redirectUris}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("redirectUris", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Forced redirect origin"), i18next.t("general:Forced redirect origin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.forcedRedirectOrigin} onChange={e => {
|
||||
this.updateApplicationField("forcedRedirectOrigin", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}}
|
||||
value={this.state.application.grantTypes}
|
||||
onChange={(value => {
|
||||
this.updateApplicationField("grantTypes", value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{id: "authorization_code", name: "Authorization Code"},
|
||||
{id: "password", name: "Password"},
|
||||
{id: "client_credentials", name: "Client Credentials"},
|
||||
{id: "token", name: "Token"},
|
||||
{id: "id_token", name: "ID Token"},
|
||||
{id: "refresh_token", name: "Refresh Token"},
|
||||
{id: "urn:ietf:params:oauth:grant-type:device_code", name: "Device Code"},
|
||||
{id: "urn:ietf:params:oauth:grant-type:jwt-bearer", name: "JWT Bearer"},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(this.state.application.category === "Agent") ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("general:Scopes"), i18next.t("general:Scopes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<ScopeTable
|
||||
title={i18next.t("general:Scopes")}
|
||||
table={this.state.application.scopes}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("scopes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
||||
options={["JWT", "JWT-Empty", "JWT-Custom", "JWT-Standard"].map((item) => Setting.getOption(item, item))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenSigningMethod === "" ? "RS256" : this.state.application.tokenSigningMethod} onChange={(value => {this.updateApplicationField("tokenSigningMethod", value);})}
|
||||
options={["RS256", "RS512", "ES256", "ES512", "ES384"].map((item) => Setting.getOption(item, item))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} disabled={this.state.application.tokenFormat !== "JWT-Custom"} mode="tags" showSearch style={{width: "100%"}} value={this.state.application.tokenFields} onChange={(value => {this.updateApplicationField("tokenFields", value);})}>
|
||||
<Option key={"signinMethod"} value={"signinMethod"}>{"SigninMethod"}</Option>
|
||||
<Option key={"provider"} value={"provider"}>{"Provider"}</Option>
|
||||
{
|
||||
[...Setting.getUserCommonFields(), "permissionNames"].map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.application.tokenFormat === "JWT-Custom" ? (<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Token attributes"), i18next.t("general:Token attributes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<TokenAttributeTable
|
||||
title={i18next.t("general:Token attributes")}
|
||||
table={this.state.application.tokenAttributes}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("tokenAttributes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.expireInHours} min={0.01} step={1} precision={2} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("expireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.refreshExpireInHours} min={0.01} step={1} precision={2} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("refreshExpireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{this.state.activeMenuKey === "saml" && (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:SAML reply URL"), i18next.t("application:Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip"))} :
|
||||
</Col>
|
||||
@@ -1308,7 +1317,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("application:Token cert"), i18next.t("application:Token cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.cert} onChange={(value => {this.updateApplicationField("cert", value);})}>
|
||||
@@ -1318,6 +1327,18 @@ class ApplicationEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Client cert"), i18next.t("application:Client cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.clientCert} onChange={(value => {this.updateApplicationField("clientCert", value);})}>
|
||||
{
|
||||
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
|
||||
{Setting.getLabel(i18next.t("application:Failed signin limit"), i18next.t("application:Failed signin limit - Tooltip"))} :
|
||||
@@ -1452,7 +1473,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Layout style={{background: "inherit", height: "100%", overflow: "auto"}}>
|
||||
{
|
||||
this.state.menuMode === "horizontal" || !this.state.menuMode ? (
|
||||
<Header style={{background: "inherit", padding: "0px", position: "sticky", top: 0}}>
|
||||
<Header style={{background: "inherit", padding: "0px", position: "sticky", top: 0, height: 38, minHeight: 38}}>
|
||||
<div className="demo-logo" />
|
||||
<Tabs
|
||||
onChange={(key) => {
|
||||
@@ -1461,9 +1482,12 @@ class ApplicationEditPage extends React.Component {
|
||||
}}
|
||||
type="card"
|
||||
activeKey={this.state.activeMenuKey}
|
||||
tabBarStyle={{marginBottom: 0}}
|
||||
items={[
|
||||
{label: i18next.t("application:Basic"), key: "basic"},
|
||||
{label: i18next.t("application:Authentication"), key: "authentication"},
|
||||
{label: "OIDC/OAuth", key: "oidc-oauth"},
|
||||
{label: "SAML", key: "saml"},
|
||||
{label: i18next.t("application:Providers"), key: "providers"},
|
||||
{label: i18next.t("application:UI Customization"), key: "ui-customization"},
|
||||
{label: i18next.t("application:Security"), key: "security"},
|
||||
@@ -1488,6 +1512,8 @@ class ApplicationEditPage extends React.Component {
|
||||
>
|
||||
<Menu.Item key="basic">{i18next.t("application:Basic")}</Menu.Item>
|
||||
<Menu.Item key="authentication">{i18next.t("application:Authentication")}</Menu.Item>
|
||||
<Menu.Item key="oidc-oauth">OIDC/OAuth</Menu.Item>
|
||||
<Menu.Item key="saml">SAML</Menu.Item>
|
||||
<Menu.Item key="providers">{i18next.t("application:Providers")}</Menu.Item>
|
||||
<Menu.Item key="ui-customization">{i18next.t("application:UI Customization")}</Menu.Item>
|
||||
<Menu.Item key="security">{i18next.t("application:Security")}</Menu.Item>
|
||||
|
||||
@@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
|
||||
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||
{name: "Name", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Display name", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "First name", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Last name", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Avatar", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "User type", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Password", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
@@ -66,6 +68,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
{name: "Country/Region", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Location", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Address", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Addresses", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Affiliation", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Title", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "ID card type", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
@@ -86,6 +89,8 @@ class OrganizationListPage extends BaseListPage {
|
||||
{name: "Balance", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Balance credit", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Balance currency", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Cart", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "Transactions", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Register type", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Register source", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
@@ -99,10 +104,15 @@ class OrganizationListPage extends BaseListPage {
|
||||
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{name: "Multi-factor authentication", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "MFA items", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "WebAuthn credentials", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "Last change password time", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
{name: "Managed accounts", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "Face ID", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "MFA accounts", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{name: "Need update password", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
{name: "IP whitelist", visible: true, viewRule: "Admin", modifyRule: "Admin"},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("record:Status code"),
|
||||
dataIndex: "statusCode",
|
||||
key: "statusCode",
|
||||
width: "120px",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("statusCode"),
|
||||
},
|
||||
|
||||
@@ -457,8 +457,8 @@ export const UserFields = ["owner", "name", "password", "display_name", "id", "t
|
||||
"is_admin", "homepage", "birthday", "gender", "password_type", "password_salt", "external_id", "avatar", "first_name", "last_name",
|
||||
"avatar_type", "permanent_avatar", "email_verified", "region", "location", "address",
|
||||
"affiliation", "title", "id_card_type", "id_card", "real_name", "is_verified", "bio", "tag", "language",
|
||||
"education", "score", "karma", "ranking", "balance", "currency", "is_default_avatar", "is_online",
|
||||
"is_forbidden", "is_deleted", "signup_application", "hash", "pre_hash", "access_key", "access_secret", "access_token",
|
||||
"education", "score", "karma", "ranking", "balance", "balance_credit", "balance_currency", "currency", "is_default_avatar", "is_online",
|
||||
"is_forbidden", "is_deleted", "signup_application", "register_type", "register_source", "hash", "pre_hash", "access_key", "access_secret", "access_token",
|
||||
"created_ip", "last_signin_time", "last_signin_ip", "github", "google", "qq", "wechat", "facebook", "dingtalk",
|
||||
"weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs", "baidu", "alipay", "casdoor", "infoflow", "apple",
|
||||
"azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon", "auth0",
|
||||
@@ -469,7 +469,7 @@ export const UserFields = ["owner", "name", "password", "display_name", "id", "t
|
||||
"wepay", "xero", "yahoo", "yammer", "yandex", "zoom", "metamask", "web3onboard", "custom", "webauthnCredentials",
|
||||
"preferred_mfa_type", "recovery_codes", "totp_secret", "mfa_phone_enabled", "mfa_email_enabled", "invitation",
|
||||
"invitation_code", "face_ids", "ldap", "properties", "roles", "permissions", "groups", "last_change_password_time",
|
||||
"last_signin_wrong_time", "signin_wrong_times", "managedAccounts", "mfaAccounts", "need_update_password",
|
||||
"last_signin_wrong_time", "signin_wrong_times", "managedAccounts", "mfaAccounts", "mfaItems", "need_update_password",
|
||||
"created_time", "updated_time", "deleted_time",
|
||||
"ip_whitelist"];
|
||||
|
||||
@@ -500,6 +500,7 @@ export const GetTranslatedUserItems = () => {
|
||||
{name: "Country/Region", label: i18next.t("user:Country/Region")},
|
||||
{name: "Location", label: i18next.t("user:Location")},
|
||||
{name: "Address", label: i18next.t("user:Address")},
|
||||
{name: "Addresses", label: i18next.t("user:Addresses")},
|
||||
{name: "Affiliation", label: i18next.t("user:Affiliation")},
|
||||
{name: "Title", label: i18next.t("general:Title")},
|
||||
{name: "ID card type", label: i18next.t("user:ID card type")},
|
||||
@@ -523,6 +524,8 @@ export const GetTranslatedUserItems = () => {
|
||||
{name: "Karma", label: i18next.t("user:Karma")},
|
||||
{name: "Ranking", label: i18next.t("user:Ranking")},
|
||||
{name: "Signup application", label: i18next.t("general:Signup application")},
|
||||
{name: "Register type", label: i18next.t("user:Register type")},
|
||||
{name: "Register source", label: i18next.t("user:Register source")},
|
||||
{name: "API key", label: i18next.t("general:API key")},
|
||||
{name: "Groups", label: i18next.t("general:Groups")},
|
||||
{name: "Roles", label: i18next.t("general:Roles")},
|
||||
@@ -537,6 +540,7 @@ export const GetTranslatedUserItems = () => {
|
||||
{name: "IP whitelist", label: i18next.t("general:IP whitelist")},
|
||||
{name: "Multi-factor authentication", label: i18next.t("mfa:Multi-factor authentication")},
|
||||
{name: "WebAuthn credentials", label: i18next.t("user:WebAuthn credentials")},
|
||||
{name: "Last change password time", label: i18next.t("user:Last change password time")},
|
||||
{name: "Managed accounts", label: i18next.t("user:Managed accounts")},
|
||||
{name: "Face ID", label: i18next.t("login:Face ID")},
|
||||
{name: "MFA accounts", label: i18next.t("user:MFA accounts")},
|
||||
@@ -554,6 +558,8 @@ export function getUserColumns() {
|
||||
transField = "Country/Region";
|
||||
} else if (field === "mfaAccounts") {
|
||||
transField = "MFA accounts";
|
||||
} else if (field === "mfaItems") {
|
||||
transField = "MFA items";
|
||||
} else if (field === "face_ids") {
|
||||
transField = "Face ID";
|
||||
} else if (field === "managedAccounts") {
|
||||
|
||||
@@ -609,13 +609,20 @@ class UserEditPage extends React.Component {
|
||||
);
|
||||
} else if (accountItem.name === "Addresses") {
|
||||
return (
|
||||
<AddressTable
|
||||
title={i18next.t("user:Addresses")}
|
||||
table={this.state.user.addresses}
|
||||
onUpdateTable={(value) => {
|
||||
this.updateUserField("addresses", value);
|
||||
}}
|
||||
/>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Addresses"), i18next.t("user:Addresses"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<AddressTable
|
||||
title={i18next.t("user:Addresses")}
|
||||
table={this.state.user.addresses}
|
||||
onUpdateTable={(value) => {
|
||||
this.updateUserField("addresses", value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Affiliation") {
|
||||
return (
|
||||
@@ -880,7 +887,7 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Transactions"), i18next.t("general:Transactions"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<TransactionTable transactions={this.state.transactions} hideTag={true} />
|
||||
<TransactionTable title={i18next.t("general:Transactions")} transactions={this.state.transactions} hideTag={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
@@ -1130,15 +1137,21 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("mfa:Multi-factor authentication"), i18next.t("mfa:Multi-factor authentication - Tooltip "))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Card size="small" title={i18next.t("mfa:Multi-factor methods")}
|
||||
extra={this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
|
||||
<PopconfirmModal
|
||||
text={i18next.t("general:Disable")}
|
||||
title={i18next.t("general:Sure to disable") + "?"}
|
||||
onConfirm={() => this.deleteMfa()}
|
||||
/> : null
|
||||
}>
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{i18next.t("mfa:Multi-factor methods")}
|
||||
{this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
|
||||
<PopconfirmModal
|
||||
text={i18next.t("general:Disable")}
|
||||
title={i18next.t("general:Sure to disable") + "?"}
|
||||
onConfirm={() => this.deleteMfa()}
|
||||
size="small"
|
||||
/> : null
|
||||
}
|
||||
</div>
|
||||
}>
|
||||
<List
|
||||
size="small"
|
||||
rowKey="mfaType"
|
||||
itemLayout="horizontal"
|
||||
dataSource={this.state.multiFactorAuths}
|
||||
|
||||
@@ -402,24 +402,27 @@ export function getProviderUrl(provider) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getProviderLogoWidget(provider) {
|
||||
export function getProviderLogoWidget(provider, options = {}) {
|
||||
if (provider === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = getProviderUrl(provider);
|
||||
if (url !== "") {
|
||||
const disableLink = options.disableLink === true;
|
||||
const imgEl = <img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />;
|
||||
|
||||
if (url !== "" && !disableLink) {
|
||||
return (
|
||||
<Tooltip title={provider.type}>
|
||||
<a target="_blank" rel="noreferrer" href={getProviderUrl(provider)}>
|
||||
<img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||
{imgEl}
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Tooltip title={provider.type}>
|
||||
<img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||
{imgEl}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,24 +23,24 @@ class RegionSelect extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
this.props.onChange(e);
|
||||
this.setState({value: e});
|
||||
}
|
||||
|
||||
render() {
|
||||
const value = this.props.value !== undefined && this.props.value !== "" ? this.props.value : (this.props.defaultValue !== undefined && this.props.defaultValue !== "" ? this.props.defaultValue : undefined);
|
||||
return (
|
||||
<Select virtual={false}
|
||||
size={this.props.size}
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{width: "100%"}}
|
||||
defaultValue={this.props.defaultValue || undefined}
|
||||
value={value}
|
||||
placeholder="Please select country/region"
|
||||
onChange={(value => {this.onChange(value);})}
|
||||
onChange={(val) => {this.onChange(val);}}
|
||||
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
filterSort={(optionA, optionB) =>
|
||||
(optionA?.label ?? "").toLowerCase().localeCompare((optionB?.label ?? "").toLowerCase())
|
||||
|
||||
@@ -268,7 +268,7 @@
|
||||
"Admin": "管理工具",
|
||||
"Affiliation URL": "工作单位URL",
|
||||
"Affiliation URL - Tooltip": "工作单位的官网URL",
|
||||
"All": "全部允许",
|
||||
"All": "全部",
|
||||
"Application": "应用",
|
||||
"Application - Tooltip": "可以访问的应用",
|
||||
"Applications": "应用",
|
||||
|
||||
44
web/src/provider/CaptchaProviderFields.js
Normal file
44
web/src/provider/CaptchaProviderFields.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2026 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 {Col, Row} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import {CaptchaPreview} from "../common/CaptchaPreview";
|
||||
|
||||
export function renderCaptchaProviderFields(provider, providerName) {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<CaptchaPreview
|
||||
owner={provider.owner}
|
||||
name={provider.name}
|
||||
provider={provider}
|
||||
providerName={providerName}
|
||||
captchaType={provider.type}
|
||||
subType={provider.subType}
|
||||
clientId={provider.clientId}
|
||||
clientSecret={provider.clientSecret}
|
||||
clientId2={provider.clientId2}
|
||||
clientSecret2={provider.clientSecret2}
|
||||
providerUrl={provider.providerUrl}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
255
web/src/provider/EmailProviderFields.js
Normal file
255
web/src/provider/EmailProviderFields.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// Copyright 2026 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 {Button, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as ProviderEditTestEmail from "../common/TestEmailWidget";
|
||||
import Editor from "../common/Editor";
|
||||
import HttpHeaderTable from "../table/HttpHeaderTable";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
export function renderEmailProviderFields(provider, updateProviderField, renderEmailMappingInput, account) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
["Custom HTTP Email", "SendGrid"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.host} onChange={e => {
|
||||
updateProviderField("host", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{["Azure ACS", "SendGrid"].includes(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={provider.port} onChange={value => {
|
||||
updateProviderField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Azure ACS", "SendGrid"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SSL mode"), i18next.t("provider:SSL mode - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "200px"}} value={provider.sslMode || "Auto"} onChange={value => {
|
||||
updateProviderField("sslMode", value);
|
||||
}}>
|
||||
<Option value="Auto">{i18next.t("general:Auto")}</Option>
|
||||
<Option value="Enable">{i18next.t("general:Enable")}</Option>
|
||||
<Option value="Disable">{i18next.t("general:Disable")}</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Enable proxy"), i18next.t("provider:Enable proxy - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={provider.enableProxy} onChange={checked => {
|
||||
updateProviderField("enableProxy", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
provider.type === "Custom HTTP Email" ? (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
|
||||
updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "GET", name: "GET"},
|
||||
{id: "POST", name: "POST"},
|
||||
{id: "PUT", name: "PUT"},
|
||||
{id: "DELETE", name: "DELETE"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : provider.issuerUrl} onChange={value => {
|
||||
updateProviderField("issuerUrl", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "application/json", name: "application/json"},
|
||||
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<HttpHeaderTable httpHeaders={provider.httpHeaders} onUpdateTable={(value) => {updateProviderField("httpHeaders", value);}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{renderEmailMappingInput()}
|
||||
</Col>
|
||||
</Row> : null}
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
<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"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.title} onChange={e => {
|
||||
updateProviderField("title", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes. <reset-link>Or click %link to reset</reset-link>")} >
|
||||
{i18next.t("general:Reset to Default")} (Text)
|
||||
</Button>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >
|
||||
{i18next.t("general:Reset to Default")} (HTML)
|
||||
</Button>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{height: "300px", margin: "10px"}}>
|
||||
<Editor
|
||||
value={provider.content}
|
||||
fillHeight
|
||||
dark
|
||||
lang="html"
|
||||
onChange={value => {
|
||||
updateProviderField("content", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={1} />
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{margin: "10px"}}>
|
||||
<div dangerouslySetInnerHTML={{__html: provider.content.replace("%s", "123456").replace("%{user.friendlyName}", Setting.getFriendlyUserName(account))}} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(`${i18next.t("provider:Email content")}-${i18next.t("general:Invitations")}`, i18next.t("provider:Email content - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => updateProviderField("metadata", "You have invited to join Casdoor. Here is your invitation code: %s, please enter in 5 minutes. Or click %link to signup")} >
|
||||
{i18next.t("general:Reset to Default")} (Text)
|
||||
</Button>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => updateProviderField("metadata", Setting.getDefaultInvitationHtmlEmailContent())} >
|
||||
{i18next.t("general:Reset to Default")} (HTML)
|
||||
</Button>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{height: "300px", margin: "10px"}}>
|
||||
<Editor
|
||||
value={provider.metadata}
|
||||
fillHeight
|
||||
dark
|
||||
lang="html"
|
||||
onChange={value => {
|
||||
updateProviderField("metadata", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={1} />
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{margin: "10px"}}>
|
||||
<div dangerouslySetInnerHTML={{__html: provider.metadata.replace("%code", "123456").replace("%s", "123456")}} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Input value={provider.receiver} placeholder={i18next.t("user:Input your email")}
|
||||
onChange={e => {
|
||||
updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
{["Azure ACS", "SendGrid"].includes(provider.type) ? null : (
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(provider)} >
|
||||
{i18next.t("provider:Test SMTP Connection")}
|
||||
</Button>
|
||||
)}
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidEmail(provider.receiver)}
|
||||
onClick={() => ProviderEditTestEmail.sendTestEmail(provider, provider.receiver)} >
|
||||
{i18next.t("provider:Send Testing Email")}
|
||||
</Button>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
48
web/src/provider/FaceIDProviderFields.js
Normal file
48
web/src/provider/FaceIDProviderFields.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2026 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 {Col, Input, Row} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function renderFaceIdProviderFields(provider, updateProviderField) {
|
||||
return (
|
||||
<>
|
||||
{["Alibaba Cloud Facebody"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.intranetEndpoint} onChange={e => {
|
||||
updateProviderField("intranetEndpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
34
web/src/provider/IDVerificationProviderFields.js
Normal file
34
web/src/provider/IDVerificationProviderFields.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2026 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 {Col, Input, Row} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function renderIDVerificationProviderFields(provider, updateProviderField) {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
56
web/src/provider/MfaProviderFields.js
Normal file
56
web/src/provider/MfaProviderFields.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2026 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 {Col, Input, InputNumber, Row} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function renderMfaProviderFields(provider, updateProviderField) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.host} placeholder="10.10.10.10" onChange={e => {
|
||||
updateProviderField("host", e.target.value);
|
||||
}} />
|
||||
</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={provider.port} onChange={value => {
|
||||
updateProviderField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:RADIUS Shared Secret - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.clientSecret} placeholder="Shared secret" onChange={e => {
|
||||
updateProviderField("clientSecret", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
103
web/src/provider/NotificationProviderFields.js
Normal file
103
web/src/provider/NotificationProviderFields.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2026 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 {Button, Col, Input, Row, Select} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as ProviderNotification from "../common/TestNotificationWidget";
|
||||
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
|
||||
export function renderNotificationProviderFields(provider, updateProviderField, getReceiverRow) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{["CUCloud"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(provider.type) ?
|
||||
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
|
||||
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.regionId} onChange={e => {
|
||||
updateProviderField("regionId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Custom HTTP"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
|
||||
updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "GET", name: "GET"},
|
||||
{id: "POST", name: "POST"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Custom HTTP", "CUCloud"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.title} onChange={e => {
|
||||
updateProviderField("title", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["Google Chat", "CUCloud"].includes(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={provider.metadata} onChange={e => {
|
||||
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"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={provider.content} onChange={e => {
|
||||
updateProviderField("content", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
{getReceiverRow(provider)}
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
onClick={() => ProviderNotification.sendTestNotification(provider)} >
|
||||
{i18next.t("provider:Send Testing Notification")}
|
||||
</Button>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
218
web/src/provider/OAuthProviderFields.js
Normal file
218
web/src/provider/OAuthProviderFields.js
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2026 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 {Col, Input, Radio, Row, Switch} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
export function renderOAuthProviderFields(provider, updateProviderField, renderUserMappingInput) {
|
||||
const getDomainLabel = provider => {
|
||||
switch (provider.category) {
|
||||
case "OAuth":
|
||||
if (provider.type === "AzureAD" || provider.type === "AzureADB2C") {
|
||||
return Setting.getLabel(i18next.t("provider:Tenant ID"), i18next.t("provider:Tenant ID - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"));
|
||||
}
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<TextArea rows={4} value={provider.emailRegex} onChange={e => {
|
||||
updateProviderField("emailRegex", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
provider.type !== "WeChat" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Use WeChat Media Platform in PC"), i18next.t("provider:Use WeChat Media Platform in PC - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch disabled={!provider.clientId} checked={provider.disableSsl} onChange={checked => {
|
||||
updateProviderField("disableSsl", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("token:Access token"), i18next.t("token:Access token - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.content} disabled={!provider.disableSsl || !provider.clientId2} onChange={e => {
|
||||
updateProviderField("content", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Follow-up action"), i18next.t("provider:Follow-up action - Tooltip"))} :
|
||||
</Col>
|
||||
<Col>
|
||||
<Radio.Group value={provider.signName}
|
||||
disabled={!provider.disableSsl || !provider.clientId || !provider.clientId2}
|
||||
buttonStyle="solid"
|
||||
onChange={e => {
|
||||
updateProviderField("signName", e.target.value);
|
||||
}}>
|
||||
<Radio.Button value="open">{i18next.t("provider:Use WeChat Open Platform to login")}</Radio.Button>
|
||||
<Radio.Button value="media">{i18next.t("provider:Use WeChat Media Platform to login")}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
{
|
||||
provider.type !== "ADFS" && provider.type !== "AzureAD"
|
||||
&& provider.type !== "AzureADB2C" && (provider.type !== "Casdoor" && provider.category !== "Storage")
|
||||
&& provider.type !== "Okta" && provider.type !== "Nextcloud" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{getDomainLabel(provider)} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.domain} onChange={e => {
|
||||
updateProviderField("domain", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
provider.type !== "Google" && provider.type !== "Lark" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{provider.type === "Google" ?
|
||||
Setting.getLabel(i18next.t("provider:Get phone number"), i18next.t("provider:Get phone number - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Use global endpoint"), i18next.t("provider:Use global endpoint - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch disabled={!provider.clientId} checked={provider.disableSsl} onChange={checked => {
|
||||
updateProviderField("disableSsl", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
provider.type.startsWith("Custom") ? (
|
||||
<React.Fragment>
|
||||
<Col>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.customAuthUrl} onChange={e => {
|
||||
updateProviderField("customAuthUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.customTokenUrl} onChange={e => {
|
||||
updateProviderField("customTokenUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.scopes} onChange={e => {
|
||||
updateProviderField("scopes", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.customUserInfoUrl} onChange={e => {
|
||||
updateProviderField("customUserInfoUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Enable PKCE"), i18next.t("provider:Enable PKCE - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Switch checked={provider.enablePkce} onChange={checked => {
|
||||
updateProviderField("enablePkce", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:User mapping"), i18next.t("provider:User mapping - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
{renderUserMappingInput()}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.customLogo} onChange={e => {
|
||||
updateProviderField("customLogo", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<a target="_blank" rel="noreferrer" href={provider.customLogo}>
|
||||
<img src={provider.customLogo} alt={provider.customLogo} height={90} style={{marginBottom: "20px"}} />
|
||||
</a>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
72
web/src/provider/PaymentProviderFields.js
Normal file
72
web/src/provider/PaymentProviderFields.js
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2026 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 {Col, Input, Row, Select} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
export function renderPaymentProviderFields(provider, updateProviderField, certs) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
(provider.type === "Alipay" || provider.type === "WeChat Pay" || provider.type === "Casdoor") ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.cert} onChange={(value => {updateProviderField("cert", value);})}>
|
||||
{
|
||||
certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
(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={provider.metadata} onChange={(value => {updateProviderField("metadata", value);})}>
|
||||
{
|
||||
certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{(provider.type === "GC" || provider.type === "FastSpring") ? (
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Input prefix={<LinkOutlined />} value={provider.host} onChange={e => {
|
||||
updateProviderField("host", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
133
web/src/provider/SamlProviderFields.js
Normal file
133
web/src/provider/SamlProviderFields.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2026 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 {Button, Col, Input, Row, Switch} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "../auth/Auth";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
export function renderSamlProviderFields(provider, updateProviderField, metadataConfig) {
|
||||
const {requestUrl, setRequestUrl, metadataLoading, fetchSamlMetadata, parseSamlMetadata} = metadataConfig;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Sign request"), i18next.t("provider:Sign request - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Switch checked={provider.enableSignAuthnRequest} onChange={checked => {
|
||||
updateProviderField("enableSignAuthnRequest", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Metadata url"), i18next.t("provider:Metadata url - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={6} >
|
||||
<Input value={requestUrl} onChange={e => {
|
||||
setRequestUrl(e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<Col span={16} >
|
||||
<Button style={{marginLeft: "10px"}} type="primary" loading={metadataLoading} onClick={() => {fetchSamlMetadata();}}>{i18next.t("general:Request")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<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={provider.metadata} onChange={e => {
|
||||
updateProviderField("metadata", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={2} />
|
||||
<Col span={2}>
|
||||
<Button type="primary" onClick={() => {parseSamlMetadata();}}>
|
||||
{i18next.t("provider:Parse")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:SAML 2.0 Endpoint (HTTP)"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.idP} onChange={e => {
|
||||
updateProviderField("idP", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Issuer URL"), i18next.t("provider:Issuer URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.issuerUrl} onChange={e => {
|
||||
updateProviderField("issuerUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SP ACS URL"), i18next.t("provider:SP ACS URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={`${authConfig.serverUrl}/api/acs`} readOnly="readonly" />
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
<Button type="primary" onClick={() => {
|
||||
copy(`${authConfig.serverUrl}/api/acs`);
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}>
|
||||
{i18next.t("general:Copy")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SP Entity ID"), i18next.t("provider:SP Entity ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21} >
|
||||
<Input value={`${authConfig.serverUrl}/api/acs`} readOnly="readonly" />
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
<Button type="primary" onClick={() => {
|
||||
copy(`${authConfig.serverUrl}/api/acs`);
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}>
|
||||
{i18next.t("general:Copy")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
182
web/src/provider/SmsProviderFields.js
Normal file
182
web/src/provider/SmsProviderFields.js
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2026 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 {Button, Col, Input, Row, Select, Switch} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as ProviderEditTestSms from "../common/TestSmsWidget";
|
||||
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
||||
import HttpHeaderTable from "../table/HttpHeaderTable";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
const SMS_PROVIDERS_WITHOUT_SIGN_NAME = ["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Msg91 SMS", "Infobip SMS"];
|
||||
const SMS_PROVIDERS_WITHOUT_TEMPLATE_CODE = ["Infobip SMS", "Custom HTTP SMS"];
|
||||
|
||||
export function renderSmsProviderFields(provider, updateProviderField, renderSmsMappingInput, account) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{SMS_PROVIDERS_WITHOUT_SIGN_NAME.includes(provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.signName} onChange={e => {
|
||||
updateProviderField("signName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{SMS_PROVIDERS_WITHOUT_TEMPLATE_CODE.includes(provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.templateCode} onChange={e => {
|
||||
updateProviderField("templateCode", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
provider.type === "Custom HTTP SMS" ? (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
|
||||
updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "GET", name: "GET"},
|
||||
{id: "POST", name: "POST"},
|
||||
{id: "PUT", name: "PUT"},
|
||||
{id: "DELETE", name: "DELETE"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : provider.issuerUrl} onChange={value => {
|
||||
updateProviderField("issuerUrl", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "application/json", name: "application/json"},
|
||||
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<HttpHeaderTable httpHeaders={provider.httpHeaders} onUpdateTable={(value) => {updateProviderField("httpHeaders", value);}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{renderSmsMappingInput()}
|
||||
</Col>
|
||||
</Row> : null}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.title} onChange={e => {
|
||||
updateProviderField("title", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Enable proxy"), i18next.t("provider:Enable proxy - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={provider.enableProxy} onChange={checked => {
|
||||
updateProviderField("enableProxy", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={4} >
|
||||
<Input.Group compact>
|
||||
<CountryCodeSelect
|
||||
style={{width: "90px"}}
|
||||
initValue={provider.content}
|
||||
onChange={(value) => {
|
||||
updateProviderField("content", value);
|
||||
}}
|
||||
countryCodes={account.organization.countryCodes}
|
||||
/>
|
||||
<Input value={provider.receiver}
|
||||
style={{width: "150px"}}
|
||||
placeholder = {i18next.t("user:Input your phone number")}
|
||||
onChange={e => {
|
||||
updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
</Input.Group>
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidPhone(provider.receiver) || (provider.type === "Custom HTTP SMS" && provider.endpoint === "")}
|
||||
onClick={() => ProviderEditTestSms.sendTestSms(provider, "+" + Setting.getCountryCode(provider.content) + provider.receiver)} >
|
||||
{i18next.t("provider:Send Testing SMS")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
112
web/src/provider/StorageProviderFields.js
Normal file
112
web/src/provider/StorageProviderFields.js
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2026 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 {Col, Input, Row} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function renderStorageProviderFields(provider, updateProviderField) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.intranetEndpoint} onChange={e => {
|
||||
updateProviderField("intranetEndpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
|
||||
updateProviderField("endpoint", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(provider.type) ?
|
||||
Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.bucket} onChange={e => {
|
||||
updateProviderField("bucket", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.pathPrefix} onChange={e => {
|
||||
updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{["Synology", "Casdoor"].includes(provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={provider.domain} disabled={provider.type === "Local File System"} onChange={e => {
|
||||
updateProviderField("domain", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Casdoor"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.content} onChange={e => {
|
||||
updateProviderField("content", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO"].includes(provider.type) ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{["Casdoor"].includes(provider.type) ?
|
||||
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
|
||||
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={provider.regionId} onChange={e => {
|
||||
updateProviderField("regionId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
48
web/src/provider/Web3ProviderFields.js
Normal file
48
web/src/provider/Web3ProviderFields.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2026 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 {Checkbox, Col, Row} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as Web3Auth from "../auth/Web3Auth";
|
||||
|
||||
export function renderWeb3ProviderFields(provider, updateProviderField) {
|
||||
const getWalletValue = () => {
|
||||
try {
|
||||
return JSON.parse(provider.metadata);
|
||||
} catch {
|
||||
return ["injected"];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
provider.type === "Web3Onboard" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Wallets"), i18next.t("provider:Wallets - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Checkbox.Group
|
||||
options={Web3Auth.getWeb3OnboardWalletsOptions()}
|
||||
value={getWalletValue()}
|
||||
onChange={options => {
|
||||
updateProviderField("metadata", JSON.stringify(options));
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
);
|
||||
}
|
||||
@@ -14,12 +14,16 @@
|
||||
|
||||
import React from "react";
|
||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Input, Row, Select, Table, Tooltip} from "antd";
|
||||
import {AutoComplete, Button, Col, Input, Row, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import RegionSelect from "../common/select/RegionSelect";
|
||||
|
||||
const {Option} = Select;
|
||||
const TAG_OPTIONS = [
|
||||
{value: "Home", label: "Home"},
|
||||
{value: "Work", label: "Work"},
|
||||
{value: "Other", label: "Other"},
|
||||
];
|
||||
|
||||
class AddressTable extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -86,16 +90,20 @@ class AddressTable extends React.Component {
|
||||
key: "tag",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
const tagOptions = TAG_OPTIONS.map(opt => ({...opt, label: opt.value === "Home" ? i18next.t("general:Home") : opt.value === "Work" ? i18next.t("user:Work") : i18next.t("user:Other")}));
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
<AutoComplete
|
||||
size="small"
|
||||
style={{width: "100%"}}
|
||||
value={text || ""}
|
||||
options={tagOptions}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "tag", value);
|
||||
}} >
|
||||
<Option value="Home">{i18next.t("general:Home")}</Option>
|
||||
<Option value="Work">{i18next.t("user:Work")}</Option>
|
||||
<Option value="Other">{i18next.t("user:Other")}</Option>
|
||||
</Select>
|
||||
}}
|
||||
onSelect={value => {
|
||||
this.updateField(table, index, "tag", value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -106,7 +114,7 @@ class AddressTable extends React.Component {
|
||||
width: "150px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "line1", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -119,7 +127,7 @@ class AddressTable extends React.Component {
|
||||
width: "150px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "line2", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -132,7 +140,7 @@ class AddressTable extends React.Component {
|
||||
width: "120px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "city", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -145,7 +153,7 @@ class AddressTable extends React.Component {
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "state", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -158,7 +166,7 @@ class AddressTable extends React.Component {
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "zipCode", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -172,6 +180,7 @@ class AddressTable extends React.Component {
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<RegionSelect
|
||||
size="small"
|
||||
value={text}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "region", value);
|
||||
|
||||
@@ -86,7 +86,7 @@ class ManagedAccountTable extends React.Component {
|
||||
render: (text, record, index) => {
|
||||
const items = this.props.applications;
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
<Select virtual={false} size="small" style={{width: "100%"}}
|
||||
value={text}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "application", value);
|
||||
@@ -105,7 +105,7 @@ class ManagedAccountTable extends React.Component {
|
||||
// width: "420px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input prefix={<LinkOutlined />} value={text} onChange={e => {
|
||||
<Input size="small" prefix={<LinkOutlined />} value={text} onChange={e => {
|
||||
this.updateField(table, index, "signinUrl", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -118,7 +118,7 @@ class ManagedAccountTable extends React.Component {
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "username", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -131,7 +131,7 @@ class ManagedAccountTable extends React.Component {
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input.Password value={text} onChange={e => {
|
||||
<Input.Password size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "password", e.target.value);
|
||||
}} />
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ class MfaAccountTable extends React.Component {
|
||||
width: "400px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "accountName", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -99,7 +99,7 @@ class MfaAccountTable extends React.Component {
|
||||
width: "300px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "issuer", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -111,7 +111,7 @@ class MfaAccountTable extends React.Component {
|
||||
key: "origin",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
<Input size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "origin", e.target.value);
|
||||
}} />
|
||||
);
|
||||
@@ -123,7 +123,7 @@ class MfaAccountTable extends React.Component {
|
||||
key: "secretKey",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input.Password value={text} onChange={e => {
|
||||
<Input.Password size="small" value={text} onChange={e => {
|
||||
this.updateField(table, index, "secretKey", e.target.value);
|
||||
}} />
|
||||
);
|
||||
|
||||
@@ -84,7 +84,7 @@ class MfaTable extends React.Component {
|
||||
key: "name",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
<Select virtual={false} size="small" style={{width: "100%"}}
|
||||
value={text}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "name", value);
|
||||
@@ -103,7 +103,7 @@ class MfaTable extends React.Component {
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
<Select virtual={false} size="small" style={{width: "100%"}}
|
||||
value={text}
|
||||
defaultValue="Optional"
|
||||
options={RuleItems.map((item) =>
|
||||
|
||||
@@ -110,7 +110,17 @@ class ProviderTable extends React.Component {
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
|
||||
return provider?.category;
|
||||
const owner = provider?.owner || this.getUserOrganization()?.name;
|
||||
const editUrl = provider && owner && provider.name ? `/providers/${owner}/${provider.name}` : null;
|
||||
const categoryText = provider?.category;
|
||||
if (editUrl && categoryText) {
|
||||
return (
|
||||
<a href={editUrl} target="_blank" rel="noopener noreferrer">
|
||||
{categoryText}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return categoryText;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -120,7 +130,17 @@ class ProviderTable extends React.Component {
|
||||
width: "80px",
|
||||
render: (text, record, index) => {
|
||||
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
|
||||
return Provider.getProviderLogoWidget(provider);
|
||||
const owner = provider?.owner || this.getUserOrganization()?.name;
|
||||
const editUrl = provider && owner && provider.name ? `/providers/${owner}/${provider.name}` : null;
|
||||
const typeWidget = Provider.getProviderLogoWidget(provider, {disableLink: !!editUrl});
|
||||
if (editUrl && typeWidget) {
|
||||
return (
|
||||
<a href={editUrl} target="_blank" rel="noopener noreferrer">
|
||||
{typeWidget}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return typeWidget;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -131,13 +131,18 @@ class TransactionTable extends React.Component {
|
||||
columns={columns}
|
||||
dataSource={this.props.transactions}
|
||||
rowKey={(record) => `${record.owner}/${record.name}`}
|
||||
size="small"
|
||||
size="middle"
|
||||
bordered
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ["10", "20", "50", "100"],
|
||||
}}
|
||||
title={this.props.title ? () => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
</div>
|
||||
) : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user