mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 02:13:50 +08:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
95f4f4cb6d | |||
511aefb706 | |||
1003639e5b | |||
fe53e90d37 | |||
8c73cb5395 | |||
06ebc04032 | |||
0ee98e2582 | |||
d25508fa56 | |||
916a55b633 | |||
a6c7b95f97 | |||
4f8dd771bc | |||
e0028f5eed | |||
6d6cbc7e6f | |||
ee8c2650c3 | |||
f3ea39d20c | |||
e78d9e5d2b |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
cache: false
|
||||
|
||||
# gen a dummy config file
|
||||
- run: touch dummy.yml
|
||||
|
@ -90,6 +90,7 @@ p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, POST, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, GET, /api/get-captcha-status, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-organization-applications, *, *
|
||||
@ -120,6 +121,8 @@ p, *, *, *, /cas, *, *
|
||||
p, *, *, *, /api/webauthn, *, *
|
||||
p, *, *, GET, /api/get-release, *, *
|
||||
p, *, *, GET, /api/get-default-application, *, *
|
||||
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||
p, *, *, *, /api/metrics, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -34,44 +35,6 @@ const (
|
||||
ResponseTypeCas = "cas"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
RelayState string `json:"relayState"`
|
||||
SamlRequest string `json:"samlRequest"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
|
||||
CaptchaType string `json:"captchaType"`
|
||||
CaptchaToken string `json:"captchaToken"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
@ -108,28 +71,28 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage())
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", authForm.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode, c.GetAcceptLanguage())
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
return
|
||||
@ -137,9 +100,9 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
||||
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
return
|
||||
@ -148,7 +111,7 @@ func (c *ApiController) Signup() {
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
lastUser := object.GetLastUser(authForm.Organization)
|
||||
|
||||
lastIdInt := -1
|
||||
if lastUser != nil {
|
||||
@ -158,7 +121,7 @@ func (c *ApiController) Signup() {
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
}
|
||||
|
||||
username := form.Username
|
||||
username := authForm.Username
|
||||
if !application.IsSignupItemVisible("Username") {
|
||||
username = id
|
||||
}
|
||||
@ -170,21 +133,21 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: form.Organization,
|
||||
Owner: authForm.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: "normal-user",
|
||||
Password: form.Password,
|
||||
DisplayName: form.Name,
|
||||
Password: authForm.Password,
|
||||
DisplayName: authForm.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
CountryCode: form.CountryCode,
|
||||
Email: authForm.Email,
|
||||
Phone: authForm.Phone,
|
||||
CountryCode: authForm.CountryCode,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
Region: form.Region,
|
||||
Affiliation: authForm.Affiliation,
|
||||
IdCard: authForm.IdCard,
|
||||
Region: authForm.Region,
|
||||
Score: initScore,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
@ -203,10 +166,10 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||
if form.FirstName != "" || form.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||
user.FirstName = form.FirstName
|
||||
user.LastName = form.LastName
|
||||
if authForm.FirstName != "" || authForm.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName)
|
||||
user.FirstName = authForm.FirstName
|
||||
user.LastName = authForm.LastName
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +186,7 @@ func (c *ApiController) Signup() {
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(authForm.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@ -56,7 +57,7 @@ func tokenToResponse(token *object.Token) *Response {
|
||||
}
|
||||
|
||||
// HandleLoggedIn ...
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
|
||||
allowed, err := object.CheckAccessPermission(userId, application)
|
||||
@ -221,21 +222,21 @@ func isProxyProviderType(providerType string) bool {
|
||||
// @Param nonce query string false nonce
|
||||
// @Param code_challenge_method query string false code_challenge_method
|
||||
// @Param code_challenge query string false code_challenge
|
||||
// @Param form body controllers.RequestForm true "Login information"
|
||||
// @Param form body controllers.AuthForm true "Login information"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @router /login [post]
|
||||
func (c *ApiController) Login() {
|
||||
resp := &Response{}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if form.Username != "" {
|
||||
if form.Type == ResponseTypeLogin {
|
||||
if authForm.Username != "" {
|
||||
if authForm.Type == ResponseTypeLogin {
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseError(c.T("account:Please sign out first"), c.GetSessionUsername())
|
||||
return
|
||||
@ -245,25 +246,25 @@ func (c *ApiController) Login() {
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
if authForm.Password == "" {
|
||||
if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(form.Username)
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
var checkDest string
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode))
|
||||
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
return
|
||||
@ -272,18 +273,18 @@ func (c *ApiController) Login() {
|
||||
// disable the verification code
|
||||
object.DisableVerificationCode(checkDest)
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
if !application.EnablePassword {
|
||||
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckToEnableCaptcha(application) {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
|
||||
var enableCaptcha bool
|
||||
if enableCaptcha = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); enableCaptcha {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -295,41 +296,42 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
password := form.Password
|
||||
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
|
||||
password := authForm.Password
|
||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
} else if authForm.Provider != "" {
|
||||
var application *object.Application
|
||||
if form.ClientId != "" {
|
||||
application = object.GetApplicationByClientId(form.ClientId)
|
||||
if authForm.ClientId != "" {
|
||||
application = object.GetApplicationByClientId(authForm.ClientId)
|
||||
} else {
|
||||
application = object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||
provider := object.GetProvider(util.GetId("admin", form.Provider))
|
||||
provider := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
providerItem := application.GetProviderItem(provider.Name)
|
||||
if !providerItem.IsProviderVisible() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
||||
@ -339,7 +341,7 @@ func (c *ApiController) Login() {
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "SAML" {
|
||||
// SAML
|
||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -354,7 +356,7 @@ func (c *ApiController) Login() {
|
||||
clientSecret = provider.ClientSecret2
|
||||
}
|
||||
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, authForm.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||
if idProvider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||
return
|
||||
@ -362,13 +364,13 @@ func (c *ApiController) Login() {
|
||||
|
||||
setHttpClient(idProvider, provider.Type)
|
||||
|
||||
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), form.State))
|
||||
if authForm.State != conf.GetConfigString("authState") && authForm.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), authForm.State))
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(form.Code)
|
||||
token, err := idProvider.GetToken(authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -386,7 +388,7 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
if form.Method == "signup" {
|
||||
if authForm.Method == "signup" {
|
||||
user := &object.User{}
|
||||
if provider.Category == "SAML" {
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||
@ -401,7 +403,7 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
@ -476,7 +478,7 @@ func (c *ApiController) Login() {
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
@ -492,7 +494,7 @@ func (c *ApiController) Login() {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
|
||||
}
|
||||
// resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
} else { // form.Method != "signup"
|
||||
} else { // authForm.Method != "signup"
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id)), userInfo)
|
||||
@ -520,21 +522,21 @@ func (c *ApiController) Login() {
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), form = %s"), util.StructToJson(form)))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), authForm = %s"), util.StructToJson(authForm)))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -610,3 +612,21 @@ func (c *ApiController) GetWebhookEventType() {
|
||||
wechatScanType = ""
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetCaptchaStatus
|
||||
// @Title GetCaptchaStatus
|
||||
// @Tag Token API
|
||||
// @Description Get Login Error Counts
|
||||
// @Param id query string true "The id ( owner/name ) of user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/get-captcha-status [get]
|
||||
func (c *ApiController) GetCaptchaStatus() {
|
||||
organization := c.Input().Get("organization")
|
||||
userId := c.Input().Get("user_id")
|
||||
user := object.GetUserByFields(organization, userId)
|
||||
var captchaEnabled bool
|
||||
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
}
|
||||
|
@ -41,18 +41,41 @@ type SessionData struct {
|
||||
}
|
||||
|
||||
func (c *ApiController) IsGlobalAdmin() bool {
|
||||
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||
|
||||
return isGlobalAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) IsAdmin() bool {
|
||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||
|
||||
return isGlobalAdmin || user.IsAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
|
||||
username := c.GetSessionUsername()
|
||||
if strings.HasPrefix(username, "app/") {
|
||||
// e.g., "app/app-casnode"
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
user := object.GetUser(username)
|
||||
user := c.getCurrentUser()
|
||||
if user == nil {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin, user
|
||||
}
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetSessionUsername ...
|
||||
|
39
controllers/prometheus.go
Normal file
39
controllers/prometheus.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// GetPrometheusInfo
|
||||
// @Title GetPrometheusInfo
|
||||
// @Tag Prometheus API
|
||||
// @Description get Prometheus Info
|
||||
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||
// @router /get-prometheus-info [get]
|
||||
func (c *ApiController) GetPrometheusInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
prometheusInfo, err := object.GetPrometheusInfo()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(prometheusInfo)
|
||||
}
|
@ -124,40 +124,6 @@ func (c *ApiController) DeleteToken() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthCode
|
||||
// @Title GetOAuthCode
|
||||
// @Tag Token API
|
||||
// @Description get OAuth code
|
||||
// @Param user_id query string true "The id ( owner/name ) of user"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param response_type query string true "OAuth response type"
|
||||
// @Param redirect_uri query string true "OAuth redirect URI"
|
||||
// @Param scope query string true "OAuth scope"
|
||||
// @Param state query string true "OAuth state"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @router /login/oauth/code [post]
|
||||
func (c *ApiController) GetOAuthCode() {
|
||||
userId := c.Input().Get("user_id")
|
||||
clientId := c.Input().Get("client_id")
|
||||
responseType := c.Input().Get("response_type")
|
||||
redirectUri := c.Input().Get("redirect_uri")
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
nonce := c.Input().Get("nonce")
|
||||
|
||||
challengeMethod := c.Input().Get("code_challenge_method")
|
||||
codeChallenge := c.Input().Get("code_challenge")
|
||||
|
||||
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
|
||||
c.ResponseError(c.T("auth:Challenge method should be S256"))
|
||||
return
|
||||
}
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host, c.GetAcceptLanguage())
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthToken
|
||||
// @Title GetOAuthToken
|
||||
// @Tag Token API
|
||||
|
@ -162,7 +162,9 @@ func (c *ApiController) UpdateUser() {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
if pass, err := checkPermissionForUpdateUser(oldUser, &user, c); !pass {
|
||||
|
||||
isAdmin := c.IsAdmin()
|
||||
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
@ -172,9 +174,7 @@ func (c *ApiController) UpdateUser() {
|
||||
columns = strings.Split(columnsStr, ",")
|
||||
}
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
affected := object.UpdateUser(id, &user, columns, isAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
}
|
||||
|
@ -1,138 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func checkPermissionForUpdateUser(oldUser, newUser *object.User, c *ApiController) (bool, string) {
|
||||
organization := object.GetOrganizationByUser(oldUser)
|
||||
var itemsChanged []*object.AccountItem
|
||||
|
||||
if oldUser.Owner != newUser.Owner {
|
||||
item := object.GetAccountItemByName("Organization", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Name != newUser.Name {
|
||||
item := object.GetAccountItemByName("Name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Id != newUser.Id {
|
||||
item := object.GetAccountItemByName("ID", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.DisplayName != newUser.DisplayName {
|
||||
item := object.GetAccountItemByName("Display name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Avatar != newUser.Avatar {
|
||||
item := object.GetAccountItemByName("Avatar", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Type != newUser.Type {
|
||||
item := object.GetAccountItemByName("User type", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
// The password is *** when not modified
|
||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||
item := object.GetAccountItemByName("Password", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Email != newUser.Email {
|
||||
item := object.GetAccountItemByName("Email", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Phone != newUser.Phone {
|
||||
item := object.GetAccountItemByName("Phone", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.CountryCode != newUser.CountryCode {
|
||||
item := object.GetAccountItemByName("Country code", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Region != newUser.Region {
|
||||
item := object.GetAccountItemByName("Country/Region", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Location != newUser.Location {
|
||||
item := object.GetAccountItemByName("Location", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Affiliation != newUser.Affiliation {
|
||||
item := object.GetAccountItemByName("Affiliation", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Title != newUser.Title {
|
||||
item := object.GetAccountItemByName("Title", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Homepage != newUser.Homepage {
|
||||
item := object.GetAccountItemByName("Homepage", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Bio != newUser.Bio {
|
||||
item := object.GetAccountItemByName("Bio", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Tag != newUser.Tag {
|
||||
item := object.GetAccountItemByName("Tag", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||
item := object.GetAccountItemByName("Signup application", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||
item := object.GetAccountItemByName("Properties", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||
item := object.GetAccountItemByName("Is admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
|
||||
item := object.GetAccountItemByName("Is global admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||
item := object.GetAccountItemByName("Is forbidden", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||
item := object.GetAccountItemByName("Is deleted", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
currentUser := c.getCurrentUser()
|
||||
if currentUser == nil && c.IsGlobalAdmin() {
|
||||
currentUser = &object.User{
|
||||
IsGlobalAdmin: true,
|
||||
}
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], currentUser, c.GetAcceptLanguage()); !pass {
|
||||
return pass, err
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -32,60 +33,29 @@ const (
|
||||
ForgetVerification = "forget"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// SendVerificationCode ...
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
||||
method := c.Ctx.Request.Form.Get("method")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if dest == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": dest.")
|
||||
return
|
||||
}
|
||||
if applicationId == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
|
||||
return
|
||||
}
|
||||
if checkType == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(applicationId, "/") {
|
||||
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
|
||||
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if checkType != "none" {
|
||||
if captchaToken == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(captchaToken, clientSecret); err != nil {
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
@ -94,7 +64,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
}
|
||||
|
||||
application := object.GetApplication(applicationId)
|
||||
application := object.GetApplication(vform.ApplicationId)
|
||||
organization := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||
if organization == nil {
|
||||
c.ResponseError(c.T("check:Organization does not exist"))
|
||||
@ -103,57 +73,57 @@ func (c *ApiController) SendVerificationCode() {
|
||||
|
||||
var user *object.User
|
||||
// checkUser != "", means method is ForgetVerification
|
||||
if checkUser != "" {
|
||||
if vform.CheckUser != "" {
|
||||
owner := application.Organization
|
||||
user = object.GetUser(util.GetId(owner, checkUser))
|
||||
user = object.GetUser(util.GetId(owner, vform.CheckUser))
|
||||
}
|
||||
|
||||
sendResp := errors.New("invalid dest type")
|
||||
|
||||
switch destType {
|
||||
switch vform.Type {
|
||||
case object.VerifyTypeEmail:
|
||||
if !util.IsEmailValid(dest) {
|
||||
if !util.IsEmailValid(vform.Dest) {
|
||||
c.ResponseError(c.T("check:Email is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest {
|
||||
vform.Dest = user.Email
|
||||
}
|
||||
|
||||
user = object.GetUserByEmail(organization.Name, dest)
|
||||
user = object.GetUserByEmail(organization.Name, vform.Dest)
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
} else if method == ResetVerification {
|
||||
} else if vform.Method == ResetVerification {
|
||||
user = c.getCurrentUser()
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
||||
case object.VerifyTypePhone:
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||
vform.Dest = user.Phone
|
||||
}
|
||||
|
||||
if user = object.GetUserByPhone(organization.Name, dest); user == nil {
|
||||
if user = object.GetUserByPhone(organization.Name, vform.Dest); user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
} else if method == ResetVerification {
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
} else if vform.Method == ResetVerification {
|
||||
if user = c.getCurrentUser(); user != nil {
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
}
|
||||
}
|
||||
|
||||
provider := application.GetSmsProvider()
|
||||
if phone, ok := util.GetE164Number(dest, countryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
|
||||
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||
return
|
||||
} else {
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
|
||||
@ -167,6 +137,38 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
return
|
||||
}
|
||||
|
||||
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
}
|
||||
|
||||
// ResetEmailOrPhone ...
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
@ -200,7 +202,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user, c.GetAcceptLanguage()); !pass {
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
@ -220,11 +222,12 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user, c.GetAcceptLanguage()); !pass {
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
c.ResponseError(result.Msg)
|
||||
return
|
||||
@ -247,88 +250,55 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
}
|
||||
|
||||
// VerifyCode
|
||||
// @Tag Account API
|
||||
// @Tag Verification API
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
if authForm.Name != "" {
|
||||
user = object.GetUserByFields(authForm.Organization, authForm.Name)
|
||||
}
|
||||
|
||||
var checkDest string
|
||||
if strings.Contains(form.Username, "@") {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
if strings.Contains(authForm.Username, "@") {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username {
|
||||
authForm.Username = user.Email
|
||||
}
|
||||
checkDest = form.Username
|
||||
checkDest = authForm.Username
|
||||
} else {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username {
|
||||
authForm.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(form.Username)
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode))
|
||||
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := object.CheckVerificationCode(checkDest, form.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
if result := object.CheckVerificationCode(checkDest, authForm.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
c.ResponseError(result.Msg)
|
||||
return
|
||||
}
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.SetSession("verifiedCode", form.Code)
|
||||
c.SetSession("verifiedCode", authForm.Code)
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||
|
||||
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||
if captchaToken == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
|
||||
return
|
||||
}
|
||||
if clientSecret == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.")
|
||||
return
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(captchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
return
|
||||
}
|
||||
|
||||
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
@ -147,9 +148,9 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
|
||||
application := object.GetApplicationByUser(user)
|
||||
var form RequestForm
|
||||
form.Type = responseType
|
||||
resp := c.HandleLoggedIn(application, user, &form)
|
||||
var authForm form.AuthForm
|
||||
authForm.Type = responseType
|
||||
resp := c.HandleLoggedIn(application, user, &authForm)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
53
form/auth.go
Normal file
53
form/auth.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package form
|
||||
|
||||
type AuthForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
RelayState string `json:"relayState"`
|
||||
SamlRequest string `json:"samlRequest"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
|
||||
CaptchaType string `json:"captchaType"`
|
||||
CaptchaToken string `json:"captchaToken"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
67
form/verification.go
Normal file
67
form/verification.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package form
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
)
|
||||
|
||||
type VerificationForm struct {
|
||||
Dest string `form:"dest"`
|
||||
Type string `form:"type"`
|
||||
CountryCode string `form:"countryCode"`
|
||||
ApplicationId string `form:"applicationId"`
|
||||
Method string `form:"method"`
|
||||
CheckUser string `form:"checkUser"`
|
||||
|
||||
CaptchaType string `form:"captchaType"`
|
||||
ClientSecret string `form:"clientSecret"`
|
||||
CaptchaToken string `form:"captchaToken"`
|
||||
}
|
||||
|
||||
const (
|
||||
SendVerifyCode = 0
|
||||
VerifyCaptcha = 1
|
||||
)
|
||||
|
||||
func (form *VerificationForm) CheckParameter(checkType int, lang string) string {
|
||||
if checkType == SendVerifyCode {
|
||||
if form.Type == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": type."
|
||||
}
|
||||
if form.Dest == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": dest."
|
||||
}
|
||||
if form.CaptchaType == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": checkType."
|
||||
}
|
||||
if !strings.Contains(form.ApplicationId, "/") {
|
||||
return i18n.Translate(lang, "verification:Wrong parameter") + ": applicationId."
|
||||
}
|
||||
}
|
||||
|
||||
if form.CaptchaType != "none" {
|
||||
if form.CaptchaToken == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": captchaToken."
|
||||
}
|
||||
if form.ClientSecret == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": clientSecret."
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
2
go.mod
2
go.mod
@ -36,6 +36,8 @@ require (
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/prometheus/client_golang v1.7.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.6.0
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Benutzername muss mindestens 2 Zeichen lang sein",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Sie haben zu oft das falsche Passwort oder den falschen Code eingegeben. Bitte warten Sie %d Minuten und versuchen Sie es erneut",
|
||||
"Your region is not allow to signup by phone": "Ihre Region ist nicht berechtigt, sich telefonisch anzumelden",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Das Passwort oder der Code ist falsch. Du hast noch %d Versuche übrig",
|
||||
"unsupported password type: %s": "Nicht unterstützter Passworttyp: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Nombre de usuario debe tener al menos 2 caracteres",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Has ingresado la contraseña o código incorrecto demasiadas veces, por favor espera %d minutos e intenta de nuevo",
|
||||
"Your region is not allow to signup by phone": "Tu región no está permitida para registrarse por teléfono",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Contraseña o código incorrecto, tienes %d intentos restantes",
|
||||
"unsupported password type: %s": "Tipo de contraseña no compatible: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
||||
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Nama pengguna harus memiliki setidaknya 2 karakter",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Anda telah memasukkan kata sandi atau kode yang salah terlalu banyak kali, mohon tunggu selama %d menit dan coba lagi",
|
||||
"Your region is not allow to signup by phone": "Wilayah Anda tidak diizinkan untuk mendaftar melalui telepon",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Kata sandi atau kode salah, Anda memiliki %d kesempatan tersisa",
|
||||
"unsupported password type: %s": "jenis sandi tidak didukung: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "ユーザー名は少なくとも2文字必要です",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "あなたは間違ったパスワードまたはコードを何度も入力しました。%d 分間待ってから再度お試しください",
|
||||
"Your region is not allow to signup by phone": "あなたの地域は電話でサインアップすることができません",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "パスワードまたはコードが間違っています。あと%d回の試行機会があります",
|
||||
"unsupported password type: %s": "サポートされていないパスワードタイプ:%s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "사용자 이름은 적어도 2개의 문자가 있어야 합니다",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "올바르지 않은 비밀번호나 코드를 여러 번 입력했습니다. %d분 동안 기다리신 후 다시 시도해주세요",
|
||||
"Your region is not allow to signup by phone": "당신의 지역은 전화로 가입할 수 없습니다",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "암호 또는 코드가 올바르지 않습니다. %d번의 기회가 남아 있습니다",
|
||||
"unsupported password type: %s": "지원되지 않는 암호 유형: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Имя пользователя должно содержать не менее 2 символов",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Вы ввели неправильный пароль или код слишком много раз, пожалуйста, подождите %d минут и попробуйте снова",
|
||||
"Your region is not allow to signup by phone": "Ваш регион не разрешает регистрацию по телефону",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Неправильный пароль или код, у вас осталось %d попыток",
|
||||
"unsupported password type: %s": "неподдерживаемый тип пароля: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "Tên đăng nhập phải có ít nhất 2 ký tự",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Bạn đã nhập sai mật khẩu hoặc mã quá nhiều lần, vui lòng đợi %d phút và thử lại",
|
||||
"Your region is not allow to signup by phone": "Vùng của bạn không được phép đăng ký bằng điện thoại",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Mật khẩu hoặc mã không chính xác, bạn còn %d lần cơ hội",
|
||||
"unsupported password type: %s": "Loại mật khẩu không được hỗ trợ: %s"
|
||||
},
|
||||
|
@ -52,6 +52,7 @@
|
||||
"Username must have at least 2 characters": "用户名至少要有2个字符",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "密码错误次数已达上限,请在 %d 分后重试",
|
||||
"Your region is not allow to signup by phone": "所在地区不支持手机号注册",
|
||||
"password or code is incorrect": "密码错误",
|
||||
"password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会",
|
||||
"unsupported password type: %s": "不支持的密码类型: %s"
|
||||
},
|
||||
|
1
main.go
1
main.go
@ -82,6 +82,7 @@ func main() {
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
go ldap.StartLdapServer()
|
||||
go object.ClearThroughputPerSecond()
|
||||
|
||||
beego.Run(fmt.Sprintf(":%v", port))
|
||||
}
|
||||
|
@ -135,6 +135,10 @@ func DeleteChat(chat *Chat) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
return DeleteChatMessages(chat.Name)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
@ -42,86 +43,86 @@ func init() {
|
||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, countryCode string, affiliation string, lang string) string {
|
||||
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Username") {
|
||||
if len(username) <= 1 {
|
||||
if len(form.Username) <= 1 {
|
||||
return i18n.Translate(lang, "check:Username must have at least 2 characters")
|
||||
}
|
||||
if unicode.IsDigit(rune(username[0])) {
|
||||
if unicode.IsDigit(rune(form.Username[0])) {
|
||||
return i18n.Translate(lang, "check:Username cannot start with a digit")
|
||||
}
|
||||
if util.IsEmailValid(username) {
|
||||
if util.IsEmailValid(form.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot be an email address")
|
||||
}
|
||||
if reWhiteSpace.MatchString(username) {
|
||||
if reWhiteSpace.MatchString(form.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot contain white spaces")
|
||||
}
|
||||
|
||||
if msg := CheckUsername(username, lang); msg != "" {
|
||||
if msg := CheckUsername(form.Username, lang); msg != "" {
|
||||
return msg
|
||||
}
|
||||
|
||||
if HasUserByField(organization.Name, "name", username) {
|
||||
if HasUserByField(organization.Name, "name", form.Username) {
|
||||
return i18n.Translate(lang, "check:Username already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) <= 5 {
|
||||
if len(form.Password) <= 5 {
|
||||
return i18n.Translate(lang, "check:Password must have at least 6 characters")
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
if email == "" {
|
||||
if form.Email == "" {
|
||||
if application.IsSignupItemRequired("Email") {
|
||||
return i18n.Translate(lang, "check:Email cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
} else if !util.IsEmailValid(email) {
|
||||
} else if !util.IsEmailValid(form.Email) {
|
||||
return i18n.Translate(lang, "check:Email is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Phone") {
|
||||
if phone == "" {
|
||||
if form.Phone == "" {
|
||||
if application.IsSignupItemRequired("Phone") {
|
||||
return i18n.Translate(lang, "check:Phone cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
} else if !util.IsPhoneAllowInRegin(countryCode, organization.CountryCodes) {
|
||||
} else if !util.IsPhoneAllowInRegin(form.CountryCode, organization.CountryCodes) {
|
||||
return i18n.Translate(lang, "check:Your region is not allow to signup by phone")
|
||||
} else if !util.IsPhoneValid(phone, countryCode) {
|
||||
} else if !util.IsPhoneValid(form.Phone, form.CountryCode) {
|
||||
return i18n.Translate(lang, "check:Phone number is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
|
||||
if firstName == "" {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (form.FirstName != "" || form.LastName != "") {
|
||||
if form.FirstName == "" {
|
||||
return i18n.Translate(lang, "check:FirstName cannot be blank")
|
||||
} else if lastName == "" {
|
||||
} else if form.LastName == "" {
|
||||
return i18n.Translate(lang, "check:LastName cannot be blank")
|
||||
}
|
||||
} else {
|
||||
if displayName == "" {
|
||||
if form.Name == "" {
|
||||
return i18n.Translate(lang, "check:DisplayName cannot be blank")
|
||||
} else if application.GetSignupItemRule("Display name") == "Real name" {
|
||||
if !isValidRealName(displayName) {
|
||||
if !isValidRealName(form.Name) {
|
||||
return i18n.Translate(lang, "check:DisplayName is not valid real name")
|
||||
}
|
||||
}
|
||||
@ -129,7 +130,7 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Affiliation") {
|
||||
if affiliation == "" {
|
||||
if form.Affiliation == "" {
|
||||
return i18n.Translate(lang, "check:Affiliation cannot be blank")
|
||||
}
|
||||
}
|
||||
@ -157,10 +158,16 @@ func checkSigninErrorTimes(user *User, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string, lang string) string {
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) string {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// check the login error times
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
if !enableCaptcha {
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
organization := GetOrganizationByUser(user)
|
||||
@ -182,7 +189,7 @@ func CheckPassword(user *User, password string, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
return recordSigninErrorInfo(user, lang)
|
||||
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
||||
} else {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
}
|
||||
@ -231,7 +238,11 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
user := GetUserByFields(organization, username)
|
||||
if user == nil || user.IsDeleted == true {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
@ -250,7 +261,7 @@ func CheckUserPassword(organization string, username string, password string, la
|
||||
return nil, msg
|
||||
}
|
||||
} else {
|
||||
if msg := CheckPassword(user, password, lang); msg != "" {
|
||||
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
}
|
||||
@ -380,7 +391,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckToEnableCaptcha(application *Application) bool {
|
||||
func CheckToEnableCaptcha(application *Application, organization, username string) bool {
|
||||
if len(application.Providers) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -390,6 +401,10 @@ func CheckToEnableCaptcha(application *Application) bool {
|
||||
continue
|
||||
}
|
||||
if providerItem.Provider.Category == "Captcha" {
|
||||
if providerItem.Rule == "Dynamic" {
|
||||
user := GetUserByFields(organization, username)
|
||||
return user != nil && user.SigninWrongTimes >= SigninWrongTimesLimit
|
||||
}
|
||||
return providerItem.Rule == "Always"
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +45,15 @@ func resetUserSigninErrorTimes(user *User) {
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string) string {
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// increase failed login count
|
||||
user.SigninWrongTimes++
|
||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||
user.SigninWrongTimes++
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
// record the latest failed login time
|
||||
@ -57,10 +63,11 @@ func recordSigninErrorInfo(user *User, lang string) string {
|
||||
// update user
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
|
||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||
if leftChances > 0 {
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
}
|
||||
|
||||
// don't show the chance error message if the user has no chance left
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
||||
}
|
||||
|
@ -143,6 +143,15 @@ func DeleteMessage(message *Message) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteChatMessages(chat string) bool {
|
||||
affected, err := adapter.Engine.Delete(&Message{Chat: chat})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Message) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
@ -210,14 +209,14 @@ func GetAccountItemByName(name string, organization *Organization) *AccountItem
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User, lang string) (bool, string) {
|
||||
func CheckAccountItemModifyRule(accountItem *AccountItem, isAdmin bool, lang string) (bool, string) {
|
||||
if accountItem == nil {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
switch accountItem.ModifyRule {
|
||||
case "Admin":
|
||||
if user == nil || !user.IsAdmin && !user.IsGlobalAdmin {
|
||||
if isAdmin {
|
||||
return false, fmt.Sprintf(i18n.Translate(lang, "organization:Only admin can modify the %s."), accountItem.Name)
|
||||
}
|
||||
case "Immutable":
|
||||
@ -299,18 +298,16 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for i, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Users[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Users[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
for i, u := range role.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Roles[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Roles[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
role.Owner = newName
|
||||
@ -326,18 +323,16 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for i, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Users[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Users[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
for i, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Roles[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Roles[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
permission.Owner = newName
|
||||
|
129
object/prometheus.go
Normal file
129
object/prometheus.go
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
type PrometheusInfo struct {
|
||||
APIThroughput []GaugeVecInfo `json:"apiThroughput"`
|
||||
APILatency []HistogramVecInfo `json:"apiLatency"`
|
||||
TotalThroughput float64 `json:"totalThroughput"`
|
||||
}
|
||||
|
||||
type GaugeVecInfo struct {
|
||||
Method string `json:"method"`
|
||||
Name string `json:"name"`
|
||||
Throughput float64 `json:"throughput"`
|
||||
}
|
||||
|
||||
type HistogramVecInfo struct {
|
||||
Name string `json:"name"`
|
||||
Method string `json:"method"`
|
||||
Count uint64 `json:"count"`
|
||||
Latency string `json:"latency"`
|
||||
}
|
||||
|
||||
var (
|
||||
APIThroughput = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_api_throughput",
|
||||
Help: "The throughput of each api access",
|
||||
}, []string{"path", "method"})
|
||||
|
||||
APILatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "casdoor_api_latency",
|
||||
Help: "API processing latency in milliseconds",
|
||||
}, []string{"path", "method"})
|
||||
|
||||
CpuUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_cpu_usage",
|
||||
Help: "Casdoor cpu usage",
|
||||
}, []string{"cpuNum"})
|
||||
|
||||
MemoryUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_memory_usage",
|
||||
Help: "Casdoor memory usage in Byte",
|
||||
}, []string{"type"})
|
||||
|
||||
TotalThroughput = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "casdoor_total_throughput",
|
||||
Help: "The total throughput of casdoor",
|
||||
})
|
||||
)
|
||||
|
||||
func ClearThroughputPerSecond() {
|
||||
// Clear the throughput every second
|
||||
ticker := time.NewTicker(time.Second)
|
||||
for range ticker.C {
|
||||
APIThroughput.Reset()
|
||||
TotalThroughput.Set(0)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPrometheusInfo() (*PrometheusInfo, error) {
|
||||
res := &PrometheusInfo{}
|
||||
metricFamilies, err := prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, metricFamily := range metricFamilies {
|
||||
switch metricFamily.GetName() {
|
||||
case "casdoor_api_throughput":
|
||||
res.APIThroughput = getGaugeVecInfo(metricFamily)
|
||||
case "casdoor_api_latency":
|
||||
res.APILatency = getHistogramVecInfo(metricFamily)
|
||||
case "casdoor_total_throughput":
|
||||
res.TotalThroughput = metricFamily.GetMetric()[0].GetGauge().GetValue()
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getHistogramVecInfo(metricFamily *io_prometheus_client.MetricFamily) []HistogramVecInfo {
|
||||
var histogramVecInfos []HistogramVecInfo
|
||||
for _, metric := range metricFamily.GetMetric() {
|
||||
sampleCount := metric.GetHistogram().GetSampleCount()
|
||||
sampleSum := metric.GetHistogram().GetSampleSum()
|
||||
latency := sampleSum / float64(sampleCount)
|
||||
histogramVecInfo := HistogramVecInfo{
|
||||
Method: metric.Label[0].GetValue(),
|
||||
Name: metric.Label[1].GetValue(),
|
||||
Count: sampleCount,
|
||||
Latency: fmt.Sprintf("%.3f", latency),
|
||||
}
|
||||
histogramVecInfos = append(histogramVecInfos, histogramVecInfo)
|
||||
}
|
||||
return histogramVecInfos
|
||||
}
|
||||
|
||||
func getGaugeVecInfo(metricFamily *io_prometheus_client.MetricFamily) []GaugeVecInfo {
|
||||
var counterVecInfos []GaugeVecInfo
|
||||
for _, metric := range metricFamily.GetMetric() {
|
||||
counterVecInfo := GaugeVecInfo{
|
||||
Method: metric.Label[0].GetValue(),
|
||||
Name: metric.Label[1].GetValue(),
|
||||
Throughput: metric.Gauge.GetValue(),
|
||||
}
|
||||
counterVecInfos = append(counterVecInfos, counterVecInfo)
|
||||
}
|
||||
return counterVecInfos
|
||||
}
|
@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
@ -182,13 +181,12 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Roles {
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Roles[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Roles[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
_, err = session.Where("name=?", role.Name).And("owner=?", role.Owner).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -202,13 +200,12 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Roles[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Roles[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
_, err = session.Where("name=?", permission.Name).And("owner=?", permission.Owner).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -425,7 +425,7 @@ func GetLastUser(owner string) *User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) bool {
|
||||
func UpdateUser(id string, user *User, columns []string, isAdmin bool) bool {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
@ -456,7 +456,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
}
|
||||
}
|
||||
if isGlobalAdmin {
|
||||
if isAdmin {
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
}
|
||||
|
||||
@ -657,13 +657,12 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Users[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Users[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
_, err = session.Where("name=?", role.Name).And("owner=?", role.Owner).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -677,13 +676,12 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Users[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Users[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
_, err = session.Where("name=?", permission.Name).And("owner=?", permission.Owner).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -179,6 +180,116 @@ func ClearUserOAuthProperties(user *User, providerType string) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang string) (bool, string) {
|
||||
organization := GetOrganizationByUser(oldUser)
|
||||
var itemsChanged []*AccountItem
|
||||
|
||||
if oldUser.Owner != newUser.Owner {
|
||||
item := GetAccountItemByName("Organization", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Name != newUser.Name {
|
||||
item := GetAccountItemByName("Name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Id != newUser.Id {
|
||||
item := GetAccountItemByName("ID", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.DisplayName != newUser.DisplayName {
|
||||
item := GetAccountItemByName("Display name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Avatar != newUser.Avatar {
|
||||
item := GetAccountItemByName("Avatar", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Type != newUser.Type {
|
||||
item := GetAccountItemByName("User type", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
// The password is *** when not modified
|
||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||
item := GetAccountItemByName("Password", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Email != newUser.Email {
|
||||
item := GetAccountItemByName("Email", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Phone != newUser.Phone {
|
||||
item := GetAccountItemByName("Phone", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.CountryCode != newUser.CountryCode {
|
||||
item := GetAccountItemByName("Country code", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Region != newUser.Region {
|
||||
item := GetAccountItemByName("Country/Region", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Location != newUser.Location {
|
||||
item := GetAccountItemByName("Location", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Affiliation != newUser.Affiliation {
|
||||
item := GetAccountItemByName("Affiliation", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Title != newUser.Title {
|
||||
item := GetAccountItemByName("Title", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Homepage != newUser.Homepage {
|
||||
item := GetAccountItemByName("Homepage", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Bio != newUser.Bio {
|
||||
item := GetAccountItemByName("Bio", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Tag != newUser.Tag {
|
||||
item := GetAccountItemByName("Tag", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||
item := GetAccountItemByName("Signup application", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||
item := GetAccountItemByName("Properties", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||
item := GetAccountItemByName("Is admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
|
||||
item := GetAccountItemByName("Is global admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||
item := GetAccountItemByName("Is forbidden", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||
item := GetAccountItemByName("Is deleted", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass {
|
||||
return pass, err
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (user *User) GetCountryCode(countryCode string) string {
|
||||
if countryCode != "" {
|
||||
return countryCode
|
||||
@ -193,3 +304,11 @@ func (user *User) GetCountryCode(countryCode string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *User) IsAdminUser() bool {
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return user.IsAdmin || user.IsGlobalAdmin
|
||||
}
|
||||
|
51
routers/prometheus_filter.go
Normal file
51
routers/prometheus_filter.go
Normal file
@ -0,0 +1,51 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type PrometheusMiddleWareWrapper struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func PrometheusMiddleWare(h http.Handler) http.Handler {
|
||||
return &PrometheusMiddleWareWrapper{
|
||||
handler: h,
|
||||
}
|
||||
}
|
||||
|
||||
func (p PrometheusMiddleWareWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
method := req.Method
|
||||
endpoint := req.URL.Path
|
||||
if strings.HasPrefix(endpoint, "/api/metrics") {
|
||||
systemInfo, err := util.GetSystemInfo()
|
||||
if err == nil {
|
||||
recordSystemInfo(systemInfo)
|
||||
}
|
||||
p.handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(endpoint, "/api") {
|
||||
start := time.Now()
|
||||
p.handler.ServeHTTP(w, req)
|
||||
latency := time.Since(start).Milliseconds()
|
||||
object.TotalThroughput.Inc()
|
||||
object.APILatency.WithLabelValues(endpoint, method).Observe(float64(latency))
|
||||
object.APIThroughput.WithLabelValues(endpoint, method).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func recordSystemInfo(systemInfo *util.SystemInfo) {
|
||||
for i, value := range systemInfo.CpuUsage {
|
||||
object.CpuUsage.WithLabelValues(fmt.Sprintf("%d", i)).Set(value)
|
||||
}
|
||||
object.MemoryUsage.WithLabelValues("memoryUsed").Set(float64(systemInfo.MemoryUsed))
|
||||
object.MemoryUsage.WithLabelValues("memoryTotal").Set(float64(systemInfo.MemoryTotal))
|
||||
}
|
@ -21,8 +21,8 @@ package routers
|
||||
|
||||
import (
|
||||
"github.com/beego/beego"
|
||||
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -57,6 +57,7 @@ func initAPI() {
|
||||
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
|
||||
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
|
||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||
|
||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||
@ -155,7 +156,6 @@ func initAPI() {
|
||||
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
|
||||
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
|
||||
@ -239,4 +239,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||
|
||||
beego.Handler("/api/metrics", promhttp.Handler())
|
||||
}
|
||||
|
@ -51,6 +51,12 @@ func StaticFilter(ctx *context.Context) {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
path2 := strings.TrimLeft(path, "web/build/images/")
|
||||
if util.FileExist(path2) {
|
||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, path2)
|
||||
return
|
||||
}
|
||||
|
||||
if !util.FileExist(path) {
|
||||
path = "web/build/index.html"
|
||||
}
|
||||
|
@ -99,6 +99,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-chat": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Chat API"
|
||||
],
|
||||
"description": "add chat",
|
||||
"operationId": "ApiController.AddChat",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the chat",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Chat"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-ldap": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -107,6 +135,34 @@
|
||||
"operationId": "ApiController.AddLdap"
|
||||
}
|
||||
},
|
||||
"/api/add-message": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Message API"
|
||||
],
|
||||
"description": "add message",
|
||||
"operationId": "ApiController.AddMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the message",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Message"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-model": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -495,6 +551,32 @@
|
||||
"operationId": "ApiController.GetCaptcha"
|
||||
}
|
||||
},
|
||||
"/api/api/get-captcha-status": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Token API"
|
||||
],
|
||||
"description": "Get Login Error Counts",
|
||||
"operationId": "ApiController.GetCaptchaStatus",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/api/get-webhook-event": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -595,6 +677,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/api/verify-code": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Verification API"
|
||||
],
|
||||
"operationId": "ApiController.VerifyCode"
|
||||
}
|
||||
},
|
||||
"/api/api/webhook": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -700,6 +790,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-chat": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Chat API"
|
||||
],
|
||||
"description": "delete chat",
|
||||
"operationId": "ApiController.DeleteChat",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the chat",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Chat"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-ldap": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -708,6 +826,34 @@
|
||||
"operationId": "ApiController.DeleteLdap"
|
||||
}
|
||||
},
|
||||
"/api/delete-message": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Message API"
|
||||
],
|
||||
"description": "delete message",
|
||||
"operationId": "ApiController.DeleteMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the message",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Message"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-model": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -1234,6 +1380,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-chat": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Chat API"
|
||||
],
|
||||
"description": "get chat",
|
||||
"operationId": "ApiController.GetChat",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the chat",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-chats": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Chat API"
|
||||
],
|
||||
"description": "get chats",
|
||||
"operationId": "ApiController.GetChats",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of chats",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-default-application": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -1357,6 +1558,61 @@
|
||||
"operationId": "ApiController.GetLdaps"
|
||||
}
|
||||
},
|
||||
"/api/get-message": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Message API"
|
||||
],
|
||||
"description": "get message",
|
||||
"operationId": "ApiController.GetMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the message",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-messages": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Message API"
|
||||
],
|
||||
"description": "get messages",
|
||||
"operationId": "ApiController.GetMessages",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of messages",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-model": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2514,7 +2770,7 @@
|
||||
"description": "Login information",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.RequestForm"
|
||||
"$ref": "#/definitions/controllers.AuthForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -2587,67 +2843,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/oauth/code": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Token API"
|
||||
],
|
||||
"description": "get OAuth code",
|
||||
"operationId": "ApiController.GetOAuthCode",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "client_id",
|
||||
"description": "OAuth client id",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "response_type",
|
||||
"description": "OAuth response type",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "redirect_uri",
|
||||
"description": "OAuth redirect URI",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "scope",
|
||||
"description": "OAuth scope",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "state",
|
||||
"description": "OAuth state",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.TokenWrapper"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/oauth/introspect": {
|
||||
"post": {
|
||||
"description": "The introspection endpoint is an OAuth 2.0 endpoint that takes a",
|
||||
@ -3056,6 +3251,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-chat": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Chat API"
|
||||
],
|
||||
"description": "update chat",
|
||||
"operationId": "ApiController.UpdateChat",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the chat",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the chat",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Chat"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-ldap": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -3064,6 +3294,41 @@
|
||||
"operationId": "ApiController.UpdateLdap"
|
||||
}
|
||||
},
|
||||
"/api/update-message": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Message API"
|
||||
],
|
||||
"description": "update message",
|
||||
"operationId": "ApiController.UpdateMessage",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the message",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the message",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Message"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-model": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -3644,11 +3909,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"2306.0xc0003a4480.false": {
|
||||
"1183.0xc000455050.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"2340.0xc0003a44b0.false": {
|
||||
"1217.0xc000455080.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@ -3660,6 +3925,10 @@
|
||||
"title": "Response",
|
||||
"type": "object"
|
||||
},
|
||||
"controllers.AuthForm": {
|
||||
"title": "AuthForm",
|
||||
"type": "object"
|
||||
},
|
||||
"controllers.EmailForm": {
|
||||
"title": "EmailForm",
|
||||
"type": "object",
|
||||
@ -3684,108 +3953,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controllers.RequestForm": {
|
||||
"title": "RequestForm",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"affiliation": {
|
||||
"type": "string"
|
||||
},
|
||||
"application": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoSignin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"captchaToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"captchaType": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"idCard": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"phoneCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"redirectUri": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"relayState": {
|
||||
"type": "string"
|
||||
},
|
||||
"samlRequest": {
|
||||
"type": "string"
|
||||
},
|
||||
"samlResponse": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controllers.Response": {
|
||||
"title": "Response",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/2306.0xc0003a4480.false"
|
||||
"$ref": "#/definitions/1183.0xc000455050.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/2340.0xc0003a44b0.false"
|
||||
"$ref": "#/definitions/1217.0xc000455080.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -4047,6 +4223,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Chat": {
|
||||
"title": "Chat",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageCount": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"user1": {
|
||||
"type": "string"
|
||||
},
|
||||
"user2": {
|
||||
"type": "string"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Header": {
|
||||
"title": "Header",
|
||||
"type": "object",
|
||||
@ -4125,6 +4347,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Message": {
|
||||
"title": "Message",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"chat": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Model": {
|
||||
"title": "Model",
|
||||
"type": "object",
|
||||
|
@ -64,11 +64,47 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-chat:
|
||||
post:
|
||||
tags:
|
||||
- Chat API
|
||||
description: add chat
|
||||
operationId: ApiController.AddChat
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the chat
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Chat'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-ldap:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.AddLdap
|
||||
/api/add-message:
|
||||
post:
|
||||
tags:
|
||||
- Message API
|
||||
description: add message
|
||||
operationId: ApiController.AddMessage
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Message'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-model:
|
||||
post:
|
||||
tags:
|
||||
@ -319,6 +355,23 @@ paths:
|
||||
tags:
|
||||
- Login API
|
||||
operationId: ApiController.GetCaptcha
|
||||
/api/api/get-captcha-status:
|
||||
get:
|
||||
tags:
|
||||
- Token API
|
||||
description: Get Login Error Counts
|
||||
operationId: ApiController.GetCaptchaStatus
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/get-webhook-event:
|
||||
get:
|
||||
tags:
|
||||
@ -385,6 +438,11 @@ paths:
|
||||
description: object
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
/api/api/verify-code:
|
||||
post:
|
||||
tags:
|
||||
- Verification API
|
||||
operationId: ApiController.VerifyCode
|
||||
/api/api/webhook:
|
||||
post:
|
||||
tags:
|
||||
@ -453,11 +511,47 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-chat:
|
||||
post:
|
||||
tags:
|
||||
- Chat API
|
||||
description: delete chat
|
||||
operationId: ApiController.DeleteChat
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the chat
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Chat'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-ldap:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.DeleteLdap
|
||||
/api/delete-message:
|
||||
post:
|
||||
tags:
|
||||
- Message API
|
||||
description: delete message
|
||||
operationId: ApiController.DeleteMessage
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Message'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-model:
|
||||
post:
|
||||
tags:
|
||||
@ -800,6 +894,42 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Cert'
|
||||
/api/get-chat:
|
||||
get:
|
||||
tags:
|
||||
- Chat API
|
||||
description: get chat
|
||||
operationId: ApiController.GetChat
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the chat
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Chat'
|
||||
/api/get-chats:
|
||||
get:
|
||||
tags:
|
||||
- Chat API
|
||||
description: get chats
|
||||
operationId: ApiController.GetChats
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of chats
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Chat'
|
||||
/api/get-default-application:
|
||||
get:
|
||||
tags:
|
||||
@ -880,6 +1010,42 @@ paths:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.GetLdaps
|
||||
/api/get-message:
|
||||
get:
|
||||
tags:
|
||||
- Message API
|
||||
description: get message
|
||||
operationId: ApiController.GetMessage
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the message
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Message'
|
||||
/api/get-messages:
|
||||
get:
|
||||
tags:
|
||||
- Message API
|
||||
description: get messages
|
||||
operationId: ApiController.GetMessages
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of messages
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Message'
|
||||
/api/get-model:
|
||||
get:
|
||||
tags:
|
||||
@ -1644,7 +1810,7 @@ paths:
|
||||
description: Login information
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.RequestForm'
|
||||
$ref: '#/definitions/controllers.AuthForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@ -1690,48 +1856,6 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
/api/login/oauth/code:
|
||||
post:
|
||||
tags:
|
||||
- Token API
|
||||
description: get OAuth code
|
||||
operationId: ApiController.GetOAuthCode
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of user
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: client_id
|
||||
description: OAuth client id
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: response_type
|
||||
description: OAuth response type
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: redirect_uri
|
||||
description: OAuth redirect URI
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: scope
|
||||
description: OAuth scope
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: state
|
||||
description: OAuth state
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
/api/login/oauth/introspect:
|
||||
post:
|
||||
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
@ -2001,11 +2125,57 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-chat:
|
||||
post:
|
||||
tags:
|
||||
- Chat API
|
||||
description: update chat
|
||||
operationId: ApiController.UpdateChat
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the chat
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the chat
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Chat'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-ldap:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.UpdateLdap
|
||||
/api/update-message:
|
||||
post:
|
||||
tags:
|
||||
- Message API
|
||||
description: update message
|
||||
operationId: ApiController.UpdateMessage
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the message
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the message
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Message'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-model:
|
||||
post:
|
||||
tags:
|
||||
@ -2385,10 +2555,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
definitions:
|
||||
2306.0xc0003a4480.false:
|
||||
1183.0xc000455050.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2340.0xc0003a44b0.false:
|
||||
1217.0xc000455080.false:
|
||||
title: "false"
|
||||
type: object
|
||||
LaravelResponse:
|
||||
@ -2397,6 +2567,9 @@ definitions:
|
||||
Response:
|
||||
title: Response
|
||||
type: object
|
||||
controllers.AuthForm:
|
||||
title: AuthForm
|
||||
type: object
|
||||
controllers.EmailForm:
|
||||
title: EmailForm
|
||||
type: object
|
||||
@ -2413,76 +2586,14 @@ definitions:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
controllers.RequestForm:
|
||||
title: RequestForm
|
||||
type: object
|
||||
properties:
|
||||
affiliation:
|
||||
type: string
|
||||
application:
|
||||
type: string
|
||||
autoSignin:
|
||||
type: boolean
|
||||
captchaToken:
|
||||
type: string
|
||||
captchaType:
|
||||
type: string
|
||||
clientId:
|
||||
type: string
|
||||
clientSecret:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
countryCode:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
emailCode:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
idCard:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
method:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
phoneCode:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
redirectUri:
|
||||
type: string
|
||||
region:
|
||||
type: string
|
||||
relayState:
|
||||
type: string
|
||||
samlRequest:
|
||||
type: string
|
||||
samlResponse:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
controllers.Response:
|
||||
title: Response
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2306.0xc0003a4480.false'
|
||||
$ref: '#/definitions/1183.0xc000455050.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2340.0xc0003a44b0.false'
|
||||
$ref: '#/definitions/1217.0xc000455080.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@ -2657,6 +2768,37 @@ definitions:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
object.Chat:
|
||||
title: Chat
|
||||
type: object
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
messageCount:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
user1:
|
||||
type: string
|
||||
user2:
|
||||
type: string
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
object.Header:
|
||||
title: Header
|
||||
type: object
|
||||
@ -2710,6 +2852,24 @@ definitions:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
object.Message:
|
||||
title: Message
|
||||
type: object
|
||||
properties:
|
||||
author:
|
||||
type: string
|
||||
chat:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
object.Model:
|
||||
title: Model
|
||||
type: object
|
||||
|
@ -76,7 +76,6 @@
|
||||
"eslint-plugin-react": "^7.31.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^13.0.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-recommended-less": "^1.0.4",
|
||||
"stylelint-config-standard": "^28.0.0"
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class AdapterListPage extends BaseListPage {
|
||||
newAdapter() {
|
||||
@ -225,7 +225,7 @@ class AdapterListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={adapters} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={adapters} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Adapters")}
|
||||
|
@ -21,7 +21,7 @@ import * as Setting from "./Setting";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ApplicationListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
@ -254,7 +254,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Applications")}
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class CertListPage extends BaseListPage {
|
||||
newCert() {
|
||||
|
@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Avatar, Input, List} from "antd";
|
||||
import {Avatar, Input, List, Spin} from "antd";
|
||||
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
@ -29,7 +30,7 @@ class ChatBox extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.messages !== this.props.messages) {
|
||||
if (prevProps.messages !== this.props.messages && this.props.messages !== null) {
|
||||
this.scrollToListItem(this.props.messages.length);
|
||||
}
|
||||
}
|
||||
@ -74,11 +75,19 @@ class ChatBox extends React.Component {
|
||||
};
|
||||
|
||||
renderList() {
|
||||
if (this.props.messages === undefined || this.props.messages === null) {
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "20%"}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={this.listContainerRef} style={{position: "relative", maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={this.props.messages === undefined ? undefined : [...this.props.messages, {}]}
|
||||
dataSource={[...this.props.messages, {}]}
|
||||
renderItem={(item, index) => {
|
||||
if (Object.keys(item).length === 0 && item.constructor === Object) {
|
||||
return <List.Item id={`chatbox-list-item-${index}`} style={{
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ChatListPage extends BaseListPage {
|
||||
newChat() {
|
||||
@ -241,7 +241,7 @@ class ChatListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={chats} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={chats} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Chats")}
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Menu} from "antd";
|
||||
import {LayoutOutlined} from "@ant-design/icons";
|
||||
import {Button, Menu} from "antd";
|
||||
import {DeleteOutlined, LayoutOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
|
||||
class ChatMenu extends React.Component {
|
||||
constructor(props) {
|
||||
@ -38,6 +38,7 @@ class ChatMenu extends React.Component {
|
||||
categories[chat.category].push(chat);
|
||||
});
|
||||
|
||||
const selectedKeys = this.state === undefined ? [] : this.state.selectedKeys;
|
||||
return Object.keys(categories).map((category, index) => {
|
||||
return {
|
||||
key: `${index}`,
|
||||
@ -45,10 +46,50 @@ class ChatMenu extends React.Component {
|
||||
label: category,
|
||||
children: categories[category].map((chat, chatIndex) => {
|
||||
const globalChatIndex = chats.indexOf(chat);
|
||||
const isSelected = selectedKeys.includes(`${index}-${chatIndex}`);
|
||||
return {
|
||||
key: `${index}-${chatIndex}`,
|
||||
index: globalChatIndex,
|
||||
label: chat.displayName,
|
||||
label: (
|
||||
<div
|
||||
className="menu-item-container"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{chat.displayName}
|
||||
{isSelected && (
|
||||
<DeleteOutlined
|
||||
className="menu-item-delete-icon"
|
||||
style={{
|
||||
visibility: "visible",
|
||||
color: "inherit",
|
||||
transition: "color 0.3s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = "inherit";
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (this.props.onDeleteChat) {
|
||||
this.props.onDeleteChat(globalChatIndex);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@ -60,8 +101,8 @@ class ChatMenu extends React.Component {
|
||||
const selectedItem = this.chatsToItems(this.props.chats)[categoryIndex].children[chatIndex];
|
||||
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
|
||||
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(selectedItem.index);
|
||||
if (this.props.onSelectChat) {
|
||||
this.props.onSelectChat(selectedItem.index);
|
||||
}
|
||||
};
|
||||
|
||||
@ -85,14 +126,40 @@ class ChatMenu extends React.Component {
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
mode="inline"
|
||||
openKeys={this.state.openKeys}
|
||||
selectedKeys={this.state.selectedKeys}
|
||||
onOpenChange={this.onOpenChange}
|
||||
onSelect={this.onSelect}
|
||||
items={items}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
style={{
|
||||
width: "calc(100% - 8px)",
|
||||
height: "40px",
|
||||
margin: "4px",
|
||||
borderColor: "rgb(229,229,229)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = "rgba(89,54,213,0.6)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = "rgba(0, 0, 0, 0.1)";
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
e.currentTarget.style.borderColor = "rgba(89,54,213,0.4)";
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
e.currentTarget.style.borderColor = "rgba(89,54,213,0.6)";
|
||||
}}
|
||||
onClick={this.props.onAddChat}
|
||||
>
|
||||
New Chat
|
||||
</Button>
|
||||
<Menu
|
||||
mode="inline"
|
||||
openKeys={this.state.openKeys}
|
||||
selectedKeys={this.state.selectedKeys}
|
||||
onOpenChange={this.onOpenChange}
|
||||
onSelect={this.onSelect}
|
||||
items={items}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ChatPage extends BaseListPage {
|
||||
newChat() {
|
||||
newChat(chat) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.applicationName,
|
||||
@ -33,8 +33,8 @@ class ChatPage extends BaseListPage {
|
||||
updatedTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
displayName: `New Chat - ${randomName}`,
|
||||
type: "Single",
|
||||
category: "Chat Category - 1",
|
||||
type: "AI",
|
||||
category: chat !== undefined ? chat.category : "Chat Category - 1",
|
||||
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
user2: "",
|
||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||
@ -42,40 +42,6 @@ class ChatPage extends BaseListPage {
|
||||
};
|
||||
}
|
||||
|
||||
// addChat() {
|
||||
// const newChat = this.newChat();
|
||||
// ChatBackend.addChat(newChat)
|
||||
// .then((res) => {
|
||||
// if (res.status === "ok") {
|
||||
// this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
|
||||
// Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||
// } else {
|
||||
// Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||
// }
|
||||
// })
|
||||
// .catch(error => {
|
||||
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// deleteChat(i) {
|
||||
// ChatBackend.deleteChat(this.state.data[i])
|
||||
// .then((res) => {
|
||||
// if (res.status === "ok") {
|
||||
// Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
// this.setState({
|
||||
// data: Setting.deleteRow(this.state.data, i),
|
||||
// pagination: {total: this.state.pagination.total - 1},
|
||||
// });
|
||||
// } else {
|
||||
// Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
// }
|
||||
// })
|
||||
// .catch(error => {
|
||||
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
// });
|
||||
// }
|
||||
|
||||
newMessage(text) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
@ -115,39 +81,116 @@ class ChatPage extends BaseListPage {
|
||||
});
|
||||
}
|
||||
|
||||
addChat(chat) {
|
||||
const newChat = this.newChat(chat);
|
||||
ChatBackend.addChat(newChat)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||
this.setState({
|
||||
chatName: newChat.name,
|
||||
messages: null,
|
||||
});
|
||||
this.getMessages(newChat.name);
|
||||
|
||||
const {pagination} = this.state;
|
||||
this.fetch({pagination});
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteChat(chats, i, chat) {
|
||||
ChatBackend.deleteChat(chat)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
const data = Setting.deleteRow(this.state.data, i);
|
||||
const j = Math.min(i, data.length - 1);
|
||||
if (j < 0) {
|
||||
this.setState({
|
||||
chatName: undefined,
|
||||
messages: undefined,
|
||||
data: data,
|
||||
});
|
||||
} else {
|
||||
const focusedChat = data[j];
|
||||
this.setState({
|
||||
chatName: focusedChat.name,
|
||||
messages: null,
|
||||
data: data,
|
||||
});
|
||||
this.getMessages(focusedChat.name);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(chats) {
|
||||
return (this.state.loading) ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
|
||||
(
|
||||
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
|
||||
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)"}}>
|
||||
<ChatMenu chats={chats} onSelect={(i) => {
|
||||
const chat = chats[i];
|
||||
this.getMessages(chat.name);
|
||||
this.setState({
|
||||
chatName: chat.name,
|
||||
});
|
||||
}} />
|
||||
</div>
|
||||
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundImage: "url(https://cdn.casbin.org/img/casdoor-logo_1185x256.png)",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "200px auto",
|
||||
backgroundBlendMode: "luminosity",
|
||||
filter: "grayscale(80%) brightness(140%) contrast(90%)",
|
||||
opacity: 0.5,
|
||||
}}>
|
||||
</div>
|
||||
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
|
||||
</div>
|
||||
const onSelectChat = (i) => {
|
||||
const chat = chats[i];
|
||||
this.setState({
|
||||
chatName: chat.name,
|
||||
messages: null,
|
||||
});
|
||||
this.getMessages(chat.name);
|
||||
};
|
||||
|
||||
const onAddChat = () => {
|
||||
const chat = this.state.data.filter(chat => chat.name === this.state.chatName)[0];
|
||||
this.addChat(chat);
|
||||
};
|
||||
|
||||
const onDeleteChat = (i) => {
|
||||
const chat = chats[i];
|
||||
this.deleteChat(chats, i, chat);
|
||||
};
|
||||
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "10%"}} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
|
||||
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)"}}>
|
||||
<ChatMenu chats={chats} onSelectChat={onSelectChat} onAddChat={onAddChat} onDeleteChat={onDeleteChat} />
|
||||
</div>
|
||||
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
|
||||
{
|
||||
this.state.messages === null ? null : (
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: -50,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundImage: "url(https://cdn.casbin.org/img/casdoor-logo_1185x256.png)",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "200px auto",
|
||||
backgroundBlendMode: "luminosity",
|
||||
filter: "grayscale(80%) brightness(140%) contrast(90%)",
|
||||
opacity: 0.5,
|
||||
}}>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import SelfForgetPage from "./auth/SelfForgetPage";
|
||||
import ForgetPage from "./auth/ForgetPage";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import CasLogout from "./auth/CasLogout";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -71,7 +72,7 @@ class EntryPage extends React.Component {
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as MessageBackend from "./backend/MessageBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class MessageListPage extends BaseListPage {
|
||||
newMessage() {
|
||||
@ -183,7 +183,7 @@ class MessageListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={messages} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={messages} rowKey={(record) => `${record.owner}/${record.name}`}size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Messages")}
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as ModelBackend from "./backend/ModelBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ModelListPage extends BaseListPage {
|
||||
newModel() {
|
||||
@ -163,7 +163,7 @@ class ModelListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={models} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered
|
||||
pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class OrganizationListPage extends BaseListPage {
|
||||
newOrganization() {
|
||||
|
@ -21,7 +21,7 @@ import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import * as Provider from "./auth/Provider";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class PaymentListPage extends BaseListPage {
|
||||
newPayment() {
|
||||
@ -243,7 +243,7 @@ class PaymentListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={payments} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={payments} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Payments")}
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class PermissionListPage extends BaseListPage {
|
||||
newPermission() {
|
||||
@ -321,7 +321,7 @@ class PermissionListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={permissions} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={permissions} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Permissions")}
|
||||
|
@ -21,7 +21,7 @@ import * as ProductBackend from "./backend/ProductBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import {EditOutlined} from "@ant-design/icons";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ProductListPage extends BaseListPage {
|
||||
newProduct() {
|
||||
|
@ -125,6 +125,8 @@ class ProviderEditPage extends React.Component {
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
}
|
||||
case "AI":
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
@ -278,17 +280,20 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("type", "Alipay");
|
||||
} else if (value === "Captcha") {
|
||||
this.updateProviderField("type", "Default");
|
||||
} else if (value === "AI") {
|
||||
this.updateProviderField("type", "OpenAI API - GPT");
|
||||
}
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: "OAuth", name: "OAuth"},
|
||||
{id: "AI", name: "AI"},
|
||||
{id: "Captcha", name: "Captcha"},
|
||||
{id: "Email", name: "Email"},
|
||||
{id: "OAuth", name: "OAuth"},
|
||||
{id: "Payment", name: "Payment"},
|
||||
{id: "SAML", name: "SAML"},
|
||||
{id: "SMS", name: "SMS"},
|
||||
{id: "Storage", name: "Storage"},
|
||||
{id: "SAML", name: "SAML"},
|
||||
{id: "Payment", name: "Payment"},
|
||||
{id: "Captcha", name: "Captcha"},
|
||||
]
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||
@ -437,16 +442,20 @@ class ProviderEditPage extends React.Component {
|
||||
{
|
||||
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel(this.state.provider)}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.category === "AI" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel(this.state.provider)}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
this.updateProviderField("clientId", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientSecretLabel(this.state.provider)}
|
||||
|
@ -21,7 +21,7 @@ import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Provider from "./auth/Provider";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ProviderListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
@ -227,7 +227,7 @@ class ProviderListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Providers")}
|
||||
|
@ -21,7 +21,7 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
||||
import i18next from "i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class ResourceListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as RoleBackend from "./backend/RoleBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class RoleListPage extends BaseListPage {
|
||||
newRole() {
|
||||
@ -196,7 +196,7 @@ class RoleListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={roles} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={roles} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Roles")}
|
||||
|
@ -19,7 +19,7 @@ import {Link} from "react-router-dom";
|
||||
import {Table, Tag} from "antd";
|
||||
import React from "react";
|
||||
import * as SessionBackend from "./backend/SessionBackend";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class SessionListPage extends BaseListPage {
|
||||
|
||||
@ -118,7 +118,7 @@ class SessionListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={sessions} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={sessions} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
|
@ -24,7 +24,6 @@ import {authConfig} from "./auth/Auth";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Conf from "./Conf";
|
||||
import * as phoneNumber from "libphonenumber-js";
|
||||
import * as path from "path-browserify";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -205,6 +204,12 @@ export const OtherProviderInfo = {
|
||||
url: "https://www.cloudflare.com/products/turnstile/",
|
||||
},
|
||||
},
|
||||
AI: {
|
||||
"OpenAI API - GPT": {
|
||||
logo: `${StaticBaseUrl}/img/social_openai.svg`,
|
||||
url: "https://platform.openai.com",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function initCountries() {
|
||||
@ -856,6 +861,10 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "GEETEST", name: "GEETEST"},
|
||||
{id: "Cloudflare Turnstile", name: "Cloudflare Turnstile"},
|
||||
]);
|
||||
} else if (category === "AI") {
|
||||
return ([
|
||||
{id: "OpenAI API - GPT", name: "OpenAI API - GPT"},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@ -888,7 +897,7 @@ export function getLoginLink(application) {
|
||||
} else if (authConfig.appName === application.name) {
|
||||
url = "/login";
|
||||
} else if (application.signinUrl === "") {
|
||||
url = path.join(application.homepageUrl, "/login");
|
||||
url = trim(application.homepageUrl, "/") + "/login";
|
||||
} else {
|
||||
url = application.signinUrl;
|
||||
}
|
||||
@ -902,10 +911,11 @@ export function renderLoginLink(application, text) {
|
||||
|
||||
export function redirectToLoginPage(application, history) {
|
||||
const loginLink = getLoginLink(application);
|
||||
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
|
||||
window.location.replace(loginLink);
|
||||
if (loginLink.startsWith("http://") || loginLink.startsWith("https://")) {
|
||||
goToLink(loginLink);
|
||||
} else {
|
||||
history.push(loginLink);
|
||||
}
|
||||
history.push(loginLink);
|
||||
}
|
||||
|
||||
function renderLink(url, text, onClick) {
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as SyncerBackend from "./backend/SyncerBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class SyncerListPage extends BaseListPage {
|
||||
newSyncer() {
|
||||
@ -253,7 +253,7 @@ class SyncerListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={syncers} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={syncers} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Syncers")}
|
||||
|
@ -17,6 +17,7 @@ import * as SystemBackend from "./backend/SystemInfo";
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import PrometheusInfoTable from "./table/PrometheusInfoTable";
|
||||
|
||||
class SystemInfo extends React.Component {
|
||||
|
||||
@ -25,6 +26,7 @@ class SystemInfo extends React.Component {
|
||||
this.state = {
|
||||
systemInfo: {cpuUsage: [], memoryUsed: 0, memoryTotal: 0},
|
||||
versionInfo: {},
|
||||
prometheusInfo: {apiThroughput: [], apiLatency: [], totalThroughput: 0},
|
||||
intervalId: null,
|
||||
loading: true,
|
||||
};
|
||||
@ -45,6 +47,11 @@ class SystemInfo extends React.Component {
|
||||
}).catch(error => {
|
||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||
});
|
||||
SystemBackend.getPrometheusInfo().then(res => {
|
||||
this.setState({
|
||||
prometheusInfo: res.data,
|
||||
});
|
||||
});
|
||||
}, 1000 * 2);
|
||||
this.setState({intervalId: id});
|
||||
}).catch(error => {
|
||||
@ -80,7 +87,10 @@ class SystemInfo extends React.Component {
|
||||
<br /> <br />
|
||||
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
|
||||
</div>;
|
||||
|
||||
const latencyUi = this.state.prometheusInfo.apiLatency.length <= 0 ? <Spin size="large" /> :
|
||||
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"latency"} />;
|
||||
const throughputUi = this.state.prometheusInfo.apiLatency.length <= 0 ? <Spin size="large" /> :
|
||||
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"throughput"} />;
|
||||
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
|
||||
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown version");
|
||||
if (this.state.versionInfo?.commitOffset > 0) {
|
||||
@ -103,6 +113,16 @@ class SystemInfo extends React.Component {
|
||||
{this.state.loading ? <Spin size="large" /> : memUi}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card title={i18next.t("system:API Latency")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : latencyUi}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card title={i18next.t("system:API Throughput")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : throughputUi}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as TokenBackend from "./backend/TokenBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class TokenListPage extends BaseListPage {
|
||||
newToken() {
|
||||
@ -222,7 +222,7 @@ class TokenListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={tokens} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={tokens} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Tokens")}
|
||||
|
@ -22,7 +22,7 @@ import * as Setting from "./Setting";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class UserListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
|
@ -244,7 +244,7 @@ class WebhookEditPage extends React.Component {
|
||||
}} >
|
||||
{
|
||||
(
|
||||
["signup", "login", "logout", "add-user", "update-user", "add-organization", "update-organization", "add-provider", "update-provider"].map((option, index) => {
|
||||
["signup", "login", "logout", "add-user", "update-user", "delete-user", "add-organization", "update-organization", "delete-organization", "add-application", "update-application", "delete-application", "add-provider", "update-provider", "delete-provider"].map((option, index) => {
|
||||
return (
|
||||
<Option key={option} value={option}>{option}</Option>
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class WebhookListPage extends BaseListPage {
|
||||
newWebhook() {
|
||||
@ -218,7 +218,7 @@ class WebhookListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={webhooks} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={webhooks} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Webhooks")}
|
||||
|
@ -139,3 +139,13 @@ export function getWechatMessageEvent() {
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getCaptchaStatus(values) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&user_id=${values["username"]}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
@ -47,7 +48,7 @@ class LoginPage extends React.Component {
|
||||
validEmailOrPhone: false,
|
||||
validEmail: false,
|
||||
loginMethod: "password",
|
||||
enableCaptchaModal: false,
|
||||
enableCaptchaModal: CaptchaRule.Never,
|
||||
openCaptchaModal: false,
|
||||
verifyCaptcha: undefined,
|
||||
samlResponse: "",
|
||||
@ -81,7 +82,13 @@ class LoginPage extends React.Component {
|
||||
if (prevProps.application !== this.props.application) {
|
||||
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
|
||||
if (captchaProviderItems) {
|
||||
this.setState({enableCaptchaModal: captchaProviderItems.some(providerItem => providerItem.rule === "Always")});
|
||||
if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) {
|
||||
this.setState({enableCaptchaModal: CaptchaRule.Always});
|
||||
} else if (captchaProviderItems.some(providerItem => providerItem.rule === "Dynamic")) {
|
||||
this.setState({enableCaptchaModal: CaptchaRule.Dynamic});
|
||||
} else {
|
||||
this.setState({enableCaptchaModal: CaptchaRule.Never});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.account && this.props.account.owner === this.props.application?.organization) {
|
||||
@ -110,6 +117,22 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
checkCaptchaStatus(values) {
|
||||
AuthBackend.getCaptchaStatus(values)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
if (res.data) {
|
||||
this.setState({
|
||||
openCaptchaModal: true,
|
||||
values: values,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
this.login(values);
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationLogin() {
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
AuthBackend.getApplicationLogin(oAuthParams)
|
||||
@ -124,7 +147,6 @@ class LoginPage extends React.Component {
|
||||
});
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
@ -255,15 +277,19 @@ class LoginPage extends React.Component {
|
||||
this.signInWithWebAuthn(username, values);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "password" && this.state.enableCaptchaModal) {
|
||||
this.setState({
|
||||
openCaptchaModal: true,
|
||||
values: values,
|
||||
});
|
||||
} else {
|
||||
this.login(values);
|
||||
if (this.state.loginMethod === "password") {
|
||||
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
|
||||
this.setState({
|
||||
openCaptchaModal: true,
|
||||
values: values,
|
||||
});
|
||||
return;
|
||||
} else if (this.state.enableCaptchaModal === CaptchaRule.Dynamic) {
|
||||
this.checkCaptchaStatus(values);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.login(values);
|
||||
}
|
||||
|
||||
login(values) {
|
||||
@ -544,13 +570,15 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
renderCaptchaModal(application) {
|
||||
if (!this.state.enableCaptchaModal) {
|
||||
if (this.state.enableCaptchaModal === CaptchaRule.Never) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = this.getCaptchaProviderItems(application)
|
||||
.filter(providerItem => providerItem.rule === "Always")
|
||||
.map(providerItem => providerItem.provider)[0];
|
||||
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
||||
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
||||
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
||||
const provider = alwaysProviderItems.length > 0
|
||||
? alwaysProviderItems[0].provider
|
||||
: dynamicProviderItems[0].provider;
|
||||
|
||||
return <CaptchaModal
|
||||
owner={provider.owner}
|
||||
@ -571,33 +599,20 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
renderFooter(application) {
|
||||
if (this.state.mode === "signup") {
|
||||
return (
|
||||
<div style={{float: "right"}}>
|
||||
{i18next.t("signup:Have account?")}
|
||||
{
|
||||
Setting.renderLoginLink(application, i18next.t("signup:sign in now"))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span style={{float: "right"}}>
|
||||
{
|
||||
!application.enableSignUp ? null : (
|
||||
<React.Fragment>
|
||||
{i18next.t("login:No account?")}
|
||||
{
|
||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span style={{float: "right"}}>
|
||||
{
|
||||
!application.enableSignUp ? null : (
|
||||
<React.Fragment>
|
||||
{i18next.t("login:No account?")}
|
||||
{
|
||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
sendSilentSigninData(data) {
|
||||
@ -746,13 +761,9 @@ class LoginPage extends React.Component {
|
||||
|
||||
renderMethodChoiceBox() {
|
||||
const application = this.getApplicationObj();
|
||||
const items = [
|
||||
{label: i18next.t("general:Password"), key: "password"},
|
||||
];
|
||||
application.enableCodeSignin ? items.push({
|
||||
label: i18next.t("login:Verification code"),
|
||||
key: "verificationCode",
|
||||
}) : null;
|
||||
const items = [];
|
||||
items.push({label: i18next.t("general:Password"), key: "password"});
|
||||
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification code"), key: "verificationCode"}) : null;
|
||||
application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
|
||||
|
||||
if (application.enableCodeSignin || application.enableWebAuthn) {
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Result} from "antd";
|
||||
import {Button, Result, Spin} from "antd";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "./Auth";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
@ -53,6 +53,14 @@ class ResultPage extends React.Component {
|
||||
render() {
|
||||
const application = this.state.application;
|
||||
|
||||
if (application === null) {
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "10%"}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
|
@ -66,7 +66,7 @@ class SignupPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
applicationName: props.match?.params?.applicationName ?? authConfig.appName,
|
||||
applicationName: (props.applicationName ?? props.match?.params?.applicationName) ?? null,
|
||||
email: "",
|
||||
phone: "",
|
||||
countryCode: "",
|
||||
@ -92,8 +92,11 @@ class SignupPage extends React.Component {
|
||||
if (this.getApplicationObj() === undefined) {
|
||||
if (this.state.applicationName !== null) {
|
||||
this.getApplication(this.state.applicationName);
|
||||
} else if (oAuthParams !== null) {
|
||||
this.getApplicationLogin(oAuthParams);
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown application name: ${this.state.applicationName}`);
|
||||
this.onUpdateApplication(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,6 +112,21 @@ class SignupPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationLogin(oAuthParams) {
|
||||
AuthBackend.getApplicationLogin(oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const application = res.data;
|
||||
this.onUpdateApplication(application);
|
||||
} else {
|
||||
this.onUpdateApplication(null);
|
||||
this.setState({
|
||||
msg: res.msg,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getResultPath(application) {
|
||||
if (authConfig.appName === application.name) {
|
||||
return "/result";
|
||||
@ -178,11 +196,7 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
isProviderVisible(providerItem) {
|
||||
if (this.state.mode === "signup") {
|
||||
return Setting.isProviderVisibleForSignUp(providerItem);
|
||||
} else {
|
||||
return Setting.isProviderVisibleForSignIn(providerItem);
|
||||
}
|
||||
return Setting.isProviderVisibleForSignUp(providerItem);
|
||||
}
|
||||
|
||||
renderFormItem(application, signupItem) {
|
||||
|
@ -33,3 +33,13 @@ export function getVersionInfo() {
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getPrometheusInfo() {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-prometheus-info `, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@ -113,9 +113,9 @@ export function setPassword(userOwner, userName, oldPassword, newPassword, code
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function sendCode(checkType, captchaToken, clientSecret, method, countryCode = "", dest, type, applicationId, checkUser = "") {
|
||||
export function sendCode(captchaType, captchaToken, clientSecret, method, countryCode = "", dest, type, applicationId, checkUser = "") {
|
||||
const formData = new FormData();
|
||||
formData.append("checkType", checkType);
|
||||
formData.append("captchaType", captchaType);
|
||||
formData.append("captchaToken", captchaToken);
|
||||
formData.append("clientSecret", clientSecret);
|
||||
formData.append("method", method);
|
||||
|
@ -170,3 +170,9 @@ export const CaptchaModal = (props) => {
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const CaptchaRule = {
|
||||
Always: "Always",
|
||||
Never: "Never",
|
||||
Dynamic: "Dynamic",
|
||||
};
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "URL der Prompt-Seite kopieren",
|
||||
"Copy signin page URL": "URL der Anmeldeseite kopieren",
|
||||
"Copy signup page URL": "URL der Anmeldeseite kopieren",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Anwendung bearbeiten",
|
||||
"Enable Email linking": "E-Mail-Verknüpfung aktivieren",
|
||||
"Enable Email linking - Tooltip": "Bei der Verwendung von Drittanbietern zur Anmeldung wird, wenn es in der Organisation einen Benutzer mit der gleichen E-Mail gibt, automatisch die Drittanbieter-Anmelde-Methode mit diesem Benutzer verbunden",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Copy prompt page URL",
|
||||
"Copy signin page URL": "Copy signin page URL",
|
||||
"Copy signup page URL": "Copy signup page URL",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable Email linking": "Enable Email linking",
|
||||
"Enable Email linking - Tooltip": "When using 3rd-party providers to log in, if there is a user in the organization with the same Email, the 3rd-party login method will be automatically associated with that user",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Copiar URL de la página del prompt",
|
||||
"Copy signin page URL": "Copiar la URL de la página de inicio de sesión",
|
||||
"Copy signup page URL": "Copiar URL de la página de registro",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Editar solicitud",
|
||||
"Enable Email linking": "Habilitar enlace de correo electrónico",
|
||||
"Enable Email linking - Tooltip": "Cuando se utilizan proveedores externos de inicio de sesión, si hay un usuario en la organización con el mismo correo electrónico, el método de inicio de sesión externo se asociará automáticamente con ese usuario",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Copier l'URL de la page de l'invite",
|
||||
"Copy signin page URL": "Copier l'URL de la page de connexion",
|
||||
"Copy signup page URL": "Copiez l'URL de la page d'inscription",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Modifier l'application",
|
||||
"Enable Email linking": "Autoriser la liaison de courrier électronique",
|
||||
"Enable Email linking - Tooltip": "Lorsque l'on utilise des fournisseurs tiers pour se connecter, s'il y a un utilisateur dans l'organisation avec la même adresse e-mail, la méthode de connexion tierce sera automatiquement associée à cet utilisateur",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Salin URL halaman prompt",
|
||||
"Copy signin page URL": "Salin URL halaman masuk",
|
||||
"Copy signup page URL": "Salin URL halaman pendaftaran",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Mengedit aplikasi",
|
||||
"Enable Email linking": "Aktifkan pengaitan email",
|
||||
"Enable Email linking - Tooltip": "Ketika menggunakan penyedia layanan pihak ketiga untuk masuk, jika ada pengguna di organisasi dengan email yang sama, metode login pihak ketiga akan secara otomatis terhubung dengan pengguna tersebut",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "プロンプトページのURLをコピーしてください",
|
||||
"Copy signin page URL": "サインインページのURLをコピーしてください",
|
||||
"Copy signup page URL": "サインアップページのURLをコピーしてください",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "アプリケーションを編集する",
|
||||
"Enable Email linking": "イーメールリンクの有効化",
|
||||
"Enable Email linking - Tooltip": "組織内に同じメールアドレスを持つユーザーがいる場合、サードパーティのログイン方法は自動的にそのユーザーに関連付けられます",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "프롬프트 페이지 URL을 복사하세요",
|
||||
"Copy signin page URL": "사인인 페이지 URL 복사",
|
||||
"Copy signup page URL": "가입 페이지 URL을 복사하세요",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "앱 편집하기",
|
||||
"Enable Email linking": "이메일 링크 사용 가능하도록 설정하기",
|
||||
"Enable Email linking - Tooltip": "3rd-party 로그인 공급자를 사용할 때, 만약 조직 내에 동일한 이메일을 사용하는 사용자가 있다면, 3rd-party 로그인 방법은 자동으로 해당 사용자와 연동됩니다",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Скопируйте URL страницы предложения",
|
||||
"Copy signin page URL": "Скопируйте URL-адрес страницы входа",
|
||||
"Copy signup page URL": "Скопируйте URL страницы регистрации",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Изменить приложение",
|
||||
"Enable Email linking": "Включить связывание электронной почты",
|
||||
"Enable Email linking - Tooltip": "При использовании сторонних провайдеров для входа, если в организации есть пользователь с такой же электронной почтой, то способ входа через стороннего провайдера автоматически будет связан с этим пользователем",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "Sao chép URL của trang nhắc nhở",
|
||||
"Copy signin page URL": "Sao chép URL trang đăng nhập",
|
||||
"Copy signup page URL": "Sao chép URL trang đăng ký",
|
||||
"Dynamic": "Dynamic",
|
||||
"Edit Application": "Chỉnh sửa ứng dụng",
|
||||
"Enable Email linking": "Cho phép liên kết Email",
|
||||
"Enable Email linking - Tooltip": "Khi sử dụng nhà cung cấp bên thứ ba để đăng nhập, nếu có người dùng trong tổ chức có cùng địa chỉ Email, phương pháp đăng nhập bên thứ ba sẽ tự động được liên kết với người dùng đó",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"Copy prompt page URL": "复制提醒页面URL",
|
||||
"Copy signin page URL": "复制登录页面URL",
|
||||
"Copy signup page URL": "复制注册页面URL",
|
||||
"Dynamic": "动态开启",
|
||||
"Edit Application": "编辑应用",
|
||||
"Enable Email linking": "自动关联邮箱相同的账号",
|
||||
"Enable Email linking - Tooltip": "使用第三方授权登录时,如果组织中存在与授权用户邮箱相同的用户,会自动关联该第三方登录方式到该用户",
|
||||
|
@ -18,7 +18,7 @@ import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import * as LdapBackend from "../backend/LdapBackend";
|
||||
import {Link} from "react-router-dom";
|
||||
import PopconfirmModal from "../PopconfirmModal";
|
||||
import PopconfirmModal from "../common/modal/PopconfirmModal";
|
||||
|
||||
class LdapTable extends React.Component {
|
||||
constructor(props) {
|
||||
|
82
web/src/table/PrometheusInfoTable.js
Normal file
82
web/src/table/PrometheusInfoTable.js
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Table} from "antd";
|
||||
|
||||
class PrometheusInfoTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
table: props.table,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const latencyColumns = [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Method",
|
||||
dataIndex: "method",
|
||||
key: "method",
|
||||
},
|
||||
{
|
||||
title: "Count",
|
||||
dataIndex: "count",
|
||||
key: "count",
|
||||
},
|
||||
{
|
||||
title: "Latency(ms)",
|
||||
dataIndex: "latency",
|
||||
key: "latency",
|
||||
},
|
||||
];
|
||||
const throughputColumns = [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Method",
|
||||
dataIndex: "method",
|
||||
key: "method",
|
||||
},
|
||||
{
|
||||
title: "Throughput",
|
||||
dataIndex: "throughput",
|
||||
key: "throughput",
|
||||
},
|
||||
];
|
||||
if (this.state.table === "latency") {
|
||||
return (
|
||||
<div style={{height: "300px", overflow: "auto"}}>
|
||||
<Table columns={latencyColumns} dataSource={this.props.prometheusInfo.apiLatency} pagination={false} />
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.table === "throughput") {
|
||||
return (
|
||||
<div style={{height: "300px", overflow: "auto"}}>
|
||||
Total Throughput: {this.props.prometheusInfo.totalThroughput}
|
||||
<Table columns={throughputColumns} dataSource={this.props.prometheusInfo.apiThroughput} pagination={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PrometheusInfoTable;
|
@ -189,6 +189,7 @@ class ProviderTable extends React.Component {
|
||||
this.updateField(table, index, "rule", value);
|
||||
}} >
|
||||
<Option key="None" value="None">{i18next.t("application:None")}</Option>
|
||||
<Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option>
|
||||
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
|
||||
</Select>
|
||||
);
|
||||
|
@ -8795,11 +8795,6 @@ pascal-case@^3.1.2:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
path-browserify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
|
||||
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
|
Reference in New Issue
Block a user