mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-01 18:00:31 +08:00
Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
95f4f4cb6d | ||
![]() |
511aefb706 | ||
![]() |
1003639e5b | ||
![]() |
fe53e90d37 | ||
![]() |
8c73cb5395 | ||
![]() |
06ebc04032 | ||
![]() |
0ee98e2582 | ||
![]() |
d25508fa56 | ||
![]() |
916a55b633 | ||
![]() |
a6c7b95f97 | ||
![]() |
4f8dd771bc | ||
![]() |
e0028f5eed | ||
![]() |
6d6cbc7e6f | ||
![]() |
ee8c2650c3 | ||
![]() |
f3ea39d20c | ||
![]() |
e78d9e5d2b | ||
![]() |
19209718ea | ||
![]() |
e75d26260a | ||
![]() |
6572ab69ce | ||
![]() |
8db87a7559 | ||
![]() |
0dcccfc19c | ||
![]() |
96219442f5 | ||
![]() |
903745c540 | ||
![]() |
df741805cd | ||
![]() |
ee5c3f3f39 | ||
![]() |
714f69be7b | ||
![]() |
0d12972e92 | ||
![]() |
78b62c28ab | ||
![]() |
5c26335fd6 | ||
![]() |
7edaeafea5 | ||
![]() |
336f3f7a7b | ||
![]() |
47dc3715f9 | ||
![]() |
7503e05a4a | ||
![]() |
b89cf1de07 | ||
![]() |
be87078c25 | ||
![]() |
faf352acc5 | ||
![]() |
0db61dd658 | ||
![]() |
ebe8ad8669 | ||
![]() |
2e01f0d10e | ||
![]() |
754fa1e745 | ||
![]() |
8b9e0ba96b | ||
![]() |
b0656aca36 |
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)
|
||||
|
@@ -20,5 +20,5 @@ staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
ldapServerPort = 389
|
||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vn
|
||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vi
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
|
@@ -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") && 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.Type)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -546,7 +548,7 @@ func (c *ApiController) Login() {
|
||||
func (c *ApiController) GetSamlLogin() {
|
||||
providerId := c.Input().Get("id")
|
||||
relayState := c.Input().Get("relayState")
|
||||
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState, c.GetAcceptLanguage())
|
||||
authURL, method, err := object.GenerateSamlRequest(providerId, relayState, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
@@ -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 ...
|
||||
|
@@ -89,7 +89,7 @@ func (c *ApiController) GetLdapUsers() {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
||||
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||
|
||||
c.ResponseOk(resp, existUuids)
|
||||
}
|
||||
|
@@ -37,8 +37,17 @@ func (c *ApiController) GetMessages() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
chat := c.Input().Get("chat")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedMessages(object.GetMessages(owner))
|
||||
var messages []*object.Message
|
||||
if chat == "" {
|
||||
messages = object.GetMessages(owner)
|
||||
} else {
|
||||
messages = object.GetChatMessages(chat)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessages(messages)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
|
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 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 ""
|
||||
}
|
4
go.mod
4
go.mod
@@ -17,6 +17,7 @@ require (
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
@@ -25,6 +26,7 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
@@ -34,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
|
||||
|
5
go.sum
5
go.sum
@@ -173,6 +173,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
|
||||
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
@@ -239,6 +241,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -677,6 +681,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
|
@@ -31,7 +31,7 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
applyToOtherLanguage("frontend", "ja", data)
|
||||
applyToOtherLanguage("frontend", "ko", data)
|
||||
applyToOtherLanguage("frontend", "ru", data)
|
||||
applyToOtherLanguage("frontend", "vn", data)
|
||||
applyToOtherLanguage("frontend", "vi", data)
|
||||
}
|
||||
|
||||
func TestGenerateI18nBackend(t *testing.T) {
|
||||
@@ -46,5 +46,5 @@ func TestGenerateI18nBackend(t *testing.T) {
|
||||
applyToOtherLanguage("backend", "ja", data)
|
||||
applyToOtherLanguage("backend", "ko", data)
|
||||
applyToOtherLanguage("backend", "ru", data)
|
||||
applyToOtherLanguage("backend", "vn", data)
|
||||
applyToOtherLanguage("backend", "vi", data)
|
||||
}
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"defaultAvatar": "",
|
||||
"defaultApplication": "",
|
||||
"tags": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"],
|
||||
"masterPassword": "",
|
||||
"initScore": 2000,
|
||||
"enableSoftDeletion": false,
|
||||
|
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))
|
||||
}
|
||||
|
@@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrlFromBuffer(organization string, username string, fileBuffer *bytes.Buffer, ext string, upload bool) string {
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s%s", organization, username, ext)
|
||||
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||
|
||||
if upload {
|
||||
_, _, err := UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer, "en")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedFileUrl
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@@ -37,3 +38,22 @@ func TestSyncPermanentAvatars(t *testing.T) {
|
||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAvatars(t *testing.T) {
|
||||
InitConfig()
|
||||
InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
users := GetUsers("casdoor")
|
||||
for _, user := range users {
|
||||
if strings.HasPrefix(user.Avatar, "http") {
|
||||
continue
|
||||
}
|
||||
|
||||
updated := user.refreshAvatar()
|
||||
if updated {
|
||||
user.PermanentAvatar = "*"
|
||||
UpdateUser(user.GetId(), user, []string{"avatar"}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
167
object/avatar_util.go
Normal file
167
object/avatar_util.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
func hasGravatar(client *http.Client, email string) (bool, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL with d=404 parameter
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
|
||||
|
||||
// Send a request to Gravatar
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check if the user has a custom Gravatar image
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
|
||||
|
||||
// Download the image
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Get the content type and determine the file extension
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
fileExtension := ""
|
||||
switch contentType {
|
||||
case "image/jpeg":
|
||||
fileExtension = ".jpg"
|
||||
case "image/png":
|
||||
fileExtension = ".png"
|
||||
case "image/gif":
|
||||
fileExtension = ".gif"
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
|
||||
}
|
||||
|
||||
// Save the image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = io.Copy(buffer, resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return buffer, fileExtension, nil
|
||||
}
|
||||
|
||||
func getColor(data []byte) color.RGBA {
|
||||
r := int(data[0]) % 256
|
||||
g := int(data[1]) % 256
|
||||
b := int(data[2]) % 256
|
||||
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
|
||||
}
|
||||
|
||||
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
|
||||
username = strings.TrimSpace(strings.ToLower(username))
|
||||
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, username)
|
||||
hashedUsername := hash.Sum(nil)
|
||||
|
||||
// Define the size of the image
|
||||
const imageSize = 420
|
||||
const cellSize = imageSize / 7
|
||||
|
||||
// Create a new image
|
||||
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
|
||||
|
||||
// Create a context
|
||||
dc := gg.NewContextForRGBA(img)
|
||||
|
||||
// Set a background color
|
||||
dc.SetColor(color.RGBA{240, 240, 240, 255})
|
||||
dc.Clear()
|
||||
|
||||
// Get avatar color
|
||||
avatarColor := getColor(hashedUsername)
|
||||
|
||||
// Draw cells
|
||||
for i := 0; i < 7; i++ {
|
||||
for j := 0; j < 7; j++ {
|
||||
if (hashedUsername[i] >> uint(j) & 1) == 1 {
|
||||
dc.SetColor(avatarColor)
|
||||
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
|
||||
dc.Fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
err := png.Encode(buffer, img)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to save image: %w", err)
|
||||
}
|
||||
|
||||
return buffer, ".png", nil
|
||||
}
|
@@ -29,6 +29,8 @@ type Chat struct {
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
User1 string `xorm:"varchar(100)" json:"user1"`
|
||||
User2 string `xorm:"varchar(100)" json:"user2"`
|
||||
Users []string `xorm:"varchar(100)" json:"users"`
|
||||
@@ -133,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()))
|
||||
}
|
||||
|
@@ -89,7 +89,7 @@ func initBuiltInOrganization() bool {
|
||||
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"},
|
||||
InitScore: 2000,
|
||||
AccountItems: getBuiltInAccountItems(),
|
||||
EnableSoftDeletion: false,
|
||||
|
@@ -268,7 +268,7 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
existUuids := GetExistUuids(owner, uuids)
|
||||
|
||||
organization := getOrganization("admin", owner)
|
||||
ldap := GetLdap(ldapId)
|
||||
@@ -327,18 +327,18 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
func GetExistUuids(owner string, uuids []string) []string {
|
||||
var users []User
|
||||
var existUuids []string
|
||||
existUuidSet := make(map[string]struct{})
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
for _, result := range results {
|
||||
if len(users) > 0 {
|
||||
for _, result := range users {
|
||||
existUuidSet[result.Ldap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ type Message struct {
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Chat string `xorm:"varchar(100)" json:"chat"`
|
||||
Chat string `xorm:"varchar(100) index" json:"chat"`
|
||||
Author string `xorm:"varchar(100)" json:"author"`
|
||||
Text string `xorm:"mediumtext" json:"text"`
|
||||
}
|
||||
@@ -67,6 +67,16 @@ func GetMessages(owner string) []*Message {
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetChatMessages(chat string) []*Message {
|
||||
messages := []*Message{}
|
||||
err := adapter.Engine.Asc("created_time").Find(&messages, &Message{Chat: chat})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetPaginationMessages(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
|
||||
messages := []*Message{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
@@ -133,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)
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -43,6 +44,26 @@ type OidcDiscovery struct {
|
||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
func isIpAddress(host string) bool {
|
||||
// Attempt to split the host and port, ignoring the error
|
||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
// If an error occurs, it might be because there's no port
|
||||
// In that case, use the original host string
|
||||
hostWithoutPort = host
|
||||
}
|
||||
|
||||
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
|
||||
ip := net.ParseIP(hostWithoutPort)
|
||||
if ip != nil {
|
||||
// The host is an IP address
|
||||
return true
|
||||
}
|
||||
|
||||
// The host is not an IP address
|
||||
return false
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
@@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
|
||||
protocol := "https://"
|
||||
if strings.HasPrefix(host, "localhost") {
|
||||
protocol = "http://"
|
||||
} else if isIpAddress(host) {
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
if host == "localhost:8000" {
|
||||
|
@@ -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
|
||||
|
@@ -29,7 +29,10 @@ import (
|
||||
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
tableName := "permission_rule"
|
||||
if len(permission.Adapter) != 0 {
|
||||
tableName = permission.Adapter
|
||||
adapterObj := getCasbinAdapter(permission.Owner, permission.Adapter)
|
||||
if adapterObj != nil && adapterObj.Table != "" {
|
||||
tableName = adapterObj.Table
|
||||
}
|
||||
}
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
driverName := conf.GetConfigString("driverName")
|
||||
@@ -130,7 +133,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
for _, subUser := range roleObj.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId})
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
|
||||
@@ -140,7 +143,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
for _, subRole := range roleObj.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId})
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})
|
||||
|
@@ -30,7 +30,10 @@ func TestProduct(t *testing.T) {
|
||||
product := GetProduct("admin/product_123")
|
||||
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
|
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
|
||||
}
|
@@ -221,6 +221,10 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
if provider.ClientSecret2 == "***" {
|
||||
session = session.Omit("client_secret2")
|
||||
}
|
||||
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := session.Update(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -230,6 +234,9 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) bool {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -23,29 +23,32 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
|
||||
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
|
||||
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
|
||||
sp, err := buildSp(provider, samlResponse, host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return assertionInfo.NameID, err
|
||||
}
|
||||
|
||||
func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method string, err error) {
|
||||
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
|
||||
provider := GetProvider(id)
|
||||
if provider.Category != "SAML" {
|
||||
return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name)
|
||||
}
|
||||
sp, err := buildSp(provider, "")
|
||||
|
||||
sp, err := buildSp(provider, "", host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -67,35 +70,22 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method stri
|
||||
return auth, method, nil
|
||||
}
|
||||
|
||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
func buildSp(provider *Provider, samlResponse string, host string) (*saml2.SAMLServiceProvider, error) {
|
||||
_, origin := getOriginFromHost(host)
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
certStore, err := buildSpCertificateStore(provider, samlResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certStore.Roots = append(certStore.Roots, idpCert)
|
||||
|
||||
sp := &saml2.SAMLServiceProvider{
|
||||
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
||||
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
||||
IDPCertificateStore: &certStore,
|
||||
SignAuthnRequests: false,
|
||||
IDPCertificateStore: &certStore,
|
||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||
}
|
||||
|
||||
if provider.Endpoint != "" {
|
||||
sp.IdentityProviderSSOURL = provider.Endpoint
|
||||
sp.IdentityProviderIssuer = provider.IssuerUrl
|
||||
@@ -104,10 +94,45 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
||||
sp.SignAuthnRequests = true
|
||||
sp.SPKeyStore = buildSpKeyStore()
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
||||
func buildSpCertificateStore(provider *Provider, samlResponse string) (dsig.MemoryX509CertificateStore, error) {
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = getCertificateFromSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{idpCert},
|
||||
}
|
||||
return certStore, nil
|
||||
}
|
||||
|
||||
func getCertificateFromSamlResponse(samlResponse string, providerType string) string {
|
||||
de, err := base64.StdEncoding.DecodeString(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -122,14 +147,3 @@ func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||
return res[1]
|
||||
}
|
||||
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
@@ -15,10 +15,12 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/xorm-io/core"
|
||||
@@ -48,6 +50,7 @@ type User struct {
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
@@ -57,7 +60,6 @@ type User struct {
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
@@ -423,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 {
|
||||
@@ -449,12 +451,12 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
}
|
||||
}
|
||||
if isGlobalAdmin {
|
||||
if isAdmin {
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
}
|
||||
|
||||
@@ -513,7 +515,10 @@ func AddUser(user *User) bool {
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
updated := user.refreshAvatar()
|
||||
if updated && user.PermanentAvatar != "*" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
}
|
||||
|
||||
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||
|
||||
@@ -652,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
|
||||
}
|
||||
@@ -672,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
|
||||
}
|
||||
@@ -693,3 +696,40 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (user *User) refreshAvatar() bool {
|
||||
var err error
|
||||
var fileBuffer *bytes.Buffer
|
||||
var ext string
|
||||
|
||||
// Gravatar + Identicon
|
||||
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
|
||||
client := proxy.ProxyHttpClient
|
||||
has, err := hasGravatar(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
|
||||
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer != nil {
|
||||
avatarUrl := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
|
||||
user.Avatar = avatarUrl
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -131,6 +132,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = userInfo.DisplayName
|
||||
}
|
||||
} else if user.DisplayName == "" {
|
||||
if userInfo.Username != "" {
|
||||
user.DisplayName = userInfo.Username
|
||||
} else {
|
||||
user.DisplayName = userInfo.Id
|
||||
}
|
||||
}
|
||||
if userInfo.Email != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
||||
@@ -173,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
|
||||
@@ -187,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
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ func GetPaymentProvider(typ string, appId string, clientSecret string, host stri
|
||||
return NewGcPaymentProvider(appId, clientSecret, host), nil
|
||||
} else if typ == "WeChat Pay" {
|
||||
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey
|
||||
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, authorityPublicKey, clientSecret, appPrivateKey)
|
||||
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
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
|
||||
|
@@ -259,3 +259,11 @@ func maskString(str string) string {
|
||||
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// GetEndPoint remove scheme from url
|
||||
func GetEndPoint(endpoint string) string {
|
||||
for _, prefix := range []string{"https://", "http://"} {
|
||||
endpoint = strings.TrimPrefix(endpoint, prefix)
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^1.5.6",
|
||||
"@ant-design/cssinjs": "^1.8.1",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@crowdin/cli": "^3.7.10",
|
||||
@@ -12,7 +12,7 @@
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"antd": "5.1.6",
|
||||
"antd": "5.2.3",
|
||||
"antd-token-previewer": "^1.1.0-22",
|
||||
"codemirror": "^5.61.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
@@ -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")}
|
||||
|
@@ -17,7 +17,7 @@ import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
@@ -44,8 +44,9 @@ import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import ChatEditPage from "./ChatEditPage";
|
||||
import ChatListPage from "./ChatListPage";
|
||||
import ChatEditPage from "./ChatEditPage";
|
||||
import ChatPage from "./ChatPage";
|
||||
import MessageEditPage from "./MessageEditPage";
|
||||
import MessageListPage from "./MessageListPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
@@ -325,12 +326,17 @@ class App extends Component {
|
||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||
"/account"
|
||||
));
|
||||
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||
"/chat"
|
||||
));
|
||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||
"/logout"));
|
||||
|
||||
const onClick = (e) => {
|
||||
if (e.key === "/account") {
|
||||
this.props.history.push("/account");
|
||||
} else if (e.key === "/chat") {
|
||||
this.props.history.push("/chat");
|
||||
} else if (e.key === "/logout") {
|
||||
this.logout();
|
||||
}
|
||||
@@ -547,6 +553,7 @@ class App extends Component {
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
@@ -622,7 +629,7 @@ class App extends Component {
|
||||
}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{Setting.isMobile() ?
|
||||
{(Setting.isMobile() || window.location.pathname === "/chat") ?
|
||||
this.renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{this.renderRouter()}
|
||||
|
@@ -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() {
|
||||
|
199
web/src/ChatBox.js
Normal file
199
web/src/ChatBox.js
Normal file
@@ -0,0 +1,199 @@
|
||||
// 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 {Avatar, Input, List, Spin} from "antd";
|
||||
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
class ChatBox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: "",
|
||||
};
|
||||
|
||||
this.listContainerRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.messages !== this.props.messages && this.props.messages !== null) {
|
||||
this.scrollToListItem(this.props.messages.length);
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.state.inputValue !== "") {
|
||||
this.send(this.state.inputValue);
|
||||
this.setState({inputValue: ""});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scrollToListItem(index) {
|
||||
const listContainerElement = this.listContainerRef.current;
|
||||
|
||||
if (!listContainerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetItem = listContainerElement.querySelector(
|
||||
`#chatbox-list-item-${index}`
|
||||
);
|
||||
|
||||
if (!targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollDistance = targetItem.offsetTop - listContainerElement.offsetTop;
|
||||
|
||||
listContainerElement.scrollTo({
|
||||
top: scrollDistance,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
send = (text) => {
|
||||
this.props.sendMessage(text);
|
||||
this.setState({inputValue: ""});
|
||||
};
|
||||
|
||||
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, {}]}
|
||||
renderItem={(item, index) => {
|
||||
if (Object.keys(item).length === 0 && item.constructor === Object) {
|
||||
return <List.Item id={`chatbox-list-item-${index}`} style={{
|
||||
height: "160px",
|
||||
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||
position: "relative",
|
||||
}} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Item id={`chatbox-list-item-${index}`} style={{
|
||||
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div style={{width: "800px", margin: "0 auto", position: "relative"}}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar style={{width: "30px", height: "30px", borderRadius: "3px"}} src={item.author === `${this.props.account.owner}/${this.props.account.name}` ? this.props.account.avatar : "https://cdn.casbin.com/casdoor/resource/built-in/admin/gpt.png"} />}
|
||||
title={<div style={{fontSize: "16px", fontWeight: "normal", lineHeight: "24px", marginTop: "-15px", marginLeft: "5px", marginRight: "80px"}}>{item.text}</div>}
|
||||
/>
|
||||
<div style={{position: "absolute", top: "0px", right: "0px"}}
|
||||
>
|
||||
<CopyOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
<LikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
<DislikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: "120px",
|
||||
background: "linear-gradient(transparent 0%, rgba(255, 255, 255, 0.8) 50%, white 100%)",
|
||||
pointerEvents: "none",
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInput() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
bottom: "90px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{position: "relative", width: "760px", marginLeft: "-280px"}}>
|
||||
<TextArea
|
||||
placeholder={"Send a message..."}
|
||||
autoSize={{maxRows: 8}}
|
||||
value={this.state.inputValue}
|
||||
onChange={(e) => this.setState({inputValue: e.target.value})}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "normal",
|
||||
lineHeight: "24px",
|
||||
width: "770px",
|
||||
height: "48px",
|
||||
borderRadius: "6px",
|
||||
borderColor: "rgb(229,229,229)",
|
||||
boxShadow: "0 0 15px rgba(0, 0, 0, 0.1)",
|
||||
paddingLeft: "17px",
|
||||
paddingRight: "17px",
|
||||
paddingTop: "12px",
|
||||
paddingBottom: "12px",
|
||||
}}
|
||||
suffix={<SendOutlined style={{color: "rgb(210,210,217"}} onClick={() => this.send(this.state.inputValue)} />}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<SendOutlined
|
||||
style={{
|
||||
color: this.state.inputValue === "" ? "rgb(210,210,217)" : "rgb(142,142,160)",
|
||||
position: "absolute",
|
||||
bottom: "17px",
|
||||
right: "17px",
|
||||
}}
|
||||
onClick={() => this.send(this.state.inputValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderList()
|
||||
}
|
||||
{
|
||||
this.renderInput()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatBox;
|
@@ -126,7 +126,33 @@ class ChatEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("general:User1 - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.type} onChange={(value => {
|
||||
this.updateChatField("type", value);
|
||||
})}
|
||||
options={[
|
||||
{value: "Single", name: i18next.t("chat:Single")},
|
||||
{value: "Group", name: i18next.t("chat:Group")},
|
||||
{value: "AI", name: i18next.t("chat:AI")},
|
||||
].map((item) => Setting.getOption(item.name, item.value))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.chat.category} onChange={e => {
|
||||
this.updateChatField("category", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})}
|
||||
@@ -136,7 +162,7 @@ class ChatEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:User2"), i18next.t("general:User2 - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("chat:User2"), i18next.t("chat:User2 - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})}
|
||||
@@ -146,7 +172,7 @@ class ChatEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:Sub users"), i18next.t("chat:Sub users - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Users"), i18next.t("chat:Users - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select mode="tags" style={{width: "100%"}} value={this.state.chat.users}
|
||||
|
@@ -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() {
|
||||
@@ -32,6 +32,8 @@ class ChatListPage extends BaseListPage {
|
||||
updatedTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
displayName: `New Chat - ${randomName}`,
|
||||
type: "Single",
|
||||
category: "Chat Category - 1",
|
||||
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
user2: "",
|
||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||
@@ -134,6 +136,30 @@ class ChatListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("displayName"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Type"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: "Single", value: "Single"},
|
||||
{text: "Group", value: "Group"},
|
||||
{text: "AI", value: "AI"},
|
||||
],
|
||||
render: (text, record, index) => {
|
||||
return i18next.t(`chat:${text}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Category"),
|
||||
dataIndex: "category",
|
||||
key: "category",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("category"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("chat:User1"),
|
||||
dataIndex: "user1",
|
||||
@@ -215,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")}
|
||||
|
167
web/src/ChatMenu.js
Normal file
167
web/src/ChatMenu.js
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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 {Button, Menu} from "antd";
|
||||
import {DeleteOutlined, LayoutOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
|
||||
class ChatMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
const openKeys = items.map((item) => item.key);
|
||||
|
||||
this.state = {
|
||||
openKeys: openKeys,
|
||||
selectedKeys: ["0-0"],
|
||||
};
|
||||
}
|
||||
|
||||
chatsToItems(chats) {
|
||||
const categories = {};
|
||||
chats.forEach((chat) => {
|
||||
if (!categories[chat.category]) {
|
||||
categories[chat.category] = [];
|
||||
}
|
||||
categories[chat.category].push(chat);
|
||||
});
|
||||
|
||||
const selectedKeys = this.state === undefined ? [] : this.state.selectedKeys;
|
||||
return Object.keys(categories).map((category, index) => {
|
||||
return {
|
||||
key: `${index}`,
|
||||
icon: <LayoutOutlined />,
|
||||
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: (
|
||||
<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>
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onSelect = (info) => {
|
||||
const [categoryIndex, chatIndex] = info.selectedKeys[0].split("-").map(Number);
|
||||
const selectedItem = this.chatsToItems(this.props.chats)[categoryIndex].children[chatIndex];
|
||||
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
|
||||
|
||||
if (this.props.onSelectChat) {
|
||||
this.props.onSelectChat(selectedItem.index);
|
||||
}
|
||||
};
|
||||
|
||||
getRootSubmenuKeys(items) {
|
||||
return items.map((item, index) => `${index}`);
|
||||
}
|
||||
|
||||
onOpenChange = (keys) => {
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
const rootSubmenuKeys = this.getRootSubmenuKeys(items);
|
||||
const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
|
||||
|
||||
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||
this.setState({openKeys: keys});
|
||||
} else {
|
||||
this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatMenu;
|
242
web/src/ChatPage.js
Normal file
242
web/src/ChatPage.js
Normal file
@@ -0,0 +1,242 @@
|
||||
// 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 {Spin} from "antd";
|
||||
import moment from "moment";
|
||||
import ChatMenu from "./ChatMenu";
|
||||
import ChatBox from "./ChatBox";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import * as MessageBackend from "./backend/MessageBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ChatPage extends BaseListPage {
|
||||
newChat(chat) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.applicationName,
|
||||
name: `chat_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
displayName: `New Chat - ${randomName}`,
|
||||
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}`],
|
||||
messageCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
newMessage(text) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.messagename,
|
||||
name: `message_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
chat: this.state.chatName,
|
||||
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
text: text,
|
||||
};
|
||||
}
|
||||
|
||||
sendMessage(text) {
|
||||
const newMessage = this.newMessage(text);
|
||||
MessageBackend.addMessage(newMessage)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.getMessages(this.state.chatName);
|
||||
} 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}`);
|
||||
});
|
||||
}
|
||||
|
||||
getMessages(chatName) {
|
||||
MessageBackend.getChatMessages(chatName)
|
||||
.then((messages) => {
|
||||
this.setState({
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
Setting.scrollToDiv(`chatbox-list-item-${messages.length}`);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.category !== undefined && params.category !== null) {
|
||||
field = "category";
|
||||
value = params.category;
|
||||
} else if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
pagination: {
|
||||
...params.pagination,
|
||||
total: res.data2,
|
||||
},
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
|
||||
const chats = res.data;
|
||||
if (this.state.chatName === undefined && chats.length > 0) {
|
||||
const chat = chats[0];
|
||||
this.getMessages(chat.name);
|
||||
this.setState({
|
||||
chatName: chat.name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ChatPage;
|
@@ -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) {
|
||||
@@ -69,9 +70,9 @@ class EntryPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||
<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} />)} />
|
||||
@@ -84,7 +85,7 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
@@ -13,10 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
||||
import {Button, Popconfirm, Table} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import i18next from "i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
class LdapSyncPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -77,9 +78,8 @@ class LdapSyncPage extends React.Component {
|
||||
LdapBackend.getLdap(this.state.organizationName, this.state.ldapId)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState((prevState) => {
|
||||
prevState.ldap = res.data;
|
||||
return prevState;
|
||||
this.setState({
|
||||
ldap: res.data,
|
||||
});
|
||||
this.getLdapUser();
|
||||
} else {
|
||||
@@ -139,22 +139,46 @@ class LdapSyncPage extends React.Component {
|
||||
dataIndex: "cn",
|
||||
key: "cn",
|
||||
sorter: (a, b) => a.cn.localeCompare(b.cn),
|
||||
render: (text, record, index) => {
|
||||
return (<div style={{display: "flex", justifyContent: "space-between"}}>
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
{this.state.existUuids.includes(record.uuid) ?
|
||||
Setting.getTag("green", i18next.t("ldap:synced")) :
|
||||
Setting.getTag("red", i18next.t("ldap:unsynced"))
|
||||
}
|
||||
</div>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:UidNumber / Uid"),
|
||||
title: "Uid",
|
||||
dataIndex: "uid",
|
||||
key: "uid",
|
||||
sorter: (a, b) => a.uid.localeCompare(b.uid),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
this.state.existUuids.includes(record.uuid) ?
|
||||
<Link to={`/users/${this.state.organizationName}/${text}`}>
|
||||
{text}
|
||||
</Link> :
|
||||
text
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "UidNumber",
|
||||
dataIndex: "uidNumber",
|
||||
key: "uidNumber",
|
||||
width: "200px",
|
||||
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
|
||||
render: (text, record, index) => {
|
||||
return `${text} / ${record.uid}`;
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Group ID"),
|
||||
dataIndex: "groupId",
|
||||
key: "groupId",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
|
||||
filters: this.buildFilter(this.state.users, "groupId"),
|
||||
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
|
||||
@@ -163,14 +187,12 @@ class LdapSyncPage extends React.Component {
|
||||
title: i18next.t("general:Email"),
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
width: "240px",
|
||||
sorter: (a, b) => a.email.localeCompare(b.email),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Phone"),
|
||||
dataIndex: "phone",
|
||||
key: "phone",
|
||||
width: "160px",
|
||||
sorter: (a, b) => a.phone.localeCompare(b.phone),
|
||||
},
|
||||
{
|
||||
@@ -183,9 +205,8 @@ class LdapSyncPage extends React.Component {
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState(prevState => {
|
||||
prevState.selectedUsers = selectedRows;
|
||||
return prevState;
|
||||
this.setState({
|
||||
selectedUsers: selectedRows,
|
||||
});
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
@@ -194,42 +215,36 @@ class LdapSyncPage extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
|
||||
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
||||
title={() => (
|
||||
<div>
|
||||
<span>{this.state.ldap?.serverName}</span>
|
||||
<Popconfirm placement={"right"}
|
||||
title={"Please confirm to sync selected users"}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" style={{marginLeft: "10px"}}>
|
||||
{i18next.t("general:Sync")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("general:Edit")} LDAP
|
||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered size="small"
|
||||
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.state.ldap?.serverName}
|
||||
<Popconfirm placement={"right"} disabled={this.state.selectedUsers.length === 0}
|
||||
title={"Please confirm to sync selected users"}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" style={{marginLeft: "10px"}} disabled={this.state.selectedUsers.length === 0}>
|
||||
{i18next.t("general:Sync")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
/>
|
||||
</div>
|
||||
</Popconfirm>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("general:Edit")} LDAP
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%", justifyContent: "center"}}>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||
|
@@ -140,7 +140,7 @@ class MessageEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("message:Author"), i18next.t("general:Author - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("message:Author"), i18next.t("message:Author - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.message.author} onChange={(value => {this.updateMessageField("author", value);})}
|
||||
|
@@ -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)}
|
||||
@@ -821,6 +830,20 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
this.state.provider.type === "WeChat Pay" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("cert", "cert")} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.cert} onChange={e => {
|
||||
this.updateProviderField("cert", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{this.getAppIdRow(this.state.provider)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
|
@@ -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;
|
||||
|
||||
@@ -42,7 +41,7 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
{label: "TiếngViệt", key: "vn", country: "VN", alt: "TiếngViệt"},
|
||||
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
@@ -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")}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Result, Row, Select, Spin, Switch} from "antd";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@@ -110,9 +110,9 @@ class UserEditPage extends React.Component {
|
||||
}
|
||||
|
||||
parseUserField(key, value) {
|
||||
// if ([].includes(key)) {
|
||||
// value = Setting.myParseInt(value);
|
||||
// }
|
||||
if (["score", "karma", "ranking"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -360,6 +360,19 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Address") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.address} onChange={e => {
|
||||
this.updateUserField("address", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Affiliation") {
|
||||
return (
|
||||
(this.state.application === null || this.state.user === null) ? null : (
|
||||
@@ -379,6 +392,32 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "ID card type") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:ID card type"), i18next.t("user:ID card type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.idCardType} onChange={e => {
|
||||
this.updateUserField("idCardType", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "ID card") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:ID card"), i18next.t("user:ID card - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.idCard} onChange={e => {
|
||||
this.updateUserField("idCard", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Homepage") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@@ -431,6 +470,97 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Language") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Language"), i18next.t("user:Language - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.language} onChange={e => {
|
||||
this.updateUserField("language", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Gender") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Gender"), i18next.t("user:Gender - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.gender} onChange={e => {
|
||||
this.updateUserField("gender", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Birthday") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Birthday"), i18next.t("user:Birthday - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.birthday} onChange={e => {
|
||||
this.updateUserField("birthday", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Education") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Education"), i18next.t("user:Education - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.education} onChange={e => {
|
||||
this.updateUserField("education", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Score") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Score"), i18next.t("user:Score - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.score} onChange={value => {
|
||||
this.updateUserField("score", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Karma") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Karma"), i18next.t("user:Karma - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.karma} onChange={value => {
|
||||
this.updateUserField("karma", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Ranking") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Ranking"), i18next.t("user:Ranking - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.ranking} onChange={value => {
|
||||
this.updateUserField("ranking", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Signup application") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@@ -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) {
|
||||
@@ -787,11 +798,11 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -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>
|
||||
{
|
||||
@@ -68,7 +76,7 @@ class ResultPage extends React.Component {
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(application);
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
}}>
|
||||
{i18next.t("login:Sign In")}
|
||||
|
@@ -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) {
|
||||
@@ -408,24 +422,27 @@ class SignupPage extends React.Component {
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("code:Please input your phone verification code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={!this.state.validPhone}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
countryCode={this.form.current?.getFieldValue("countryCode")}
|
||||
/>
|
||||
</Form.Item>
|
||||
{
|
||||
signupItem.rule !== "No verification" &&
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("code:Please input your phone verification code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={!this.state.validPhone}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
countryCode={this.form.current?.getFieldValue("countryCode")}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else if (signupItem.name === "Password") {
|
||||
|
@@ -24,6 +24,16 @@ export function getMessages(owner, page = "", pageSize = "", field = "", value =
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getChatMessages(chat) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-messages?chat=${chat}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getMessage(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-message?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
|
@@ -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",
|
||||
};
|
||||
|
@@ -22,7 +22,7 @@ import id from "./locales/id/data.json";
|
||||
import ja from "./locales/ja/data.json";
|
||||
import ko from "./locales/ko/data.json";
|
||||
import ru from "./locales/ru/data.json";
|
||||
import vn from "./locales/vn/data.json";
|
||||
import vi from "./locales/vi/data.json";
|
||||
import * as Conf from "./Conf";
|
||||
import {initReactI18next} from "react-i18next";
|
||||
|
||||
@@ -36,7 +36,7 @@ const resources = {
|
||||
ja: ja,
|
||||
ko: ko,
|
||||
ru: ru,
|
||||
vn: vn,
|
||||
vi: vi,
|
||||
};
|
||||
|
||||
function initLanguage() {
|
||||
@@ -80,8 +80,8 @@ function initLanguage() {
|
||||
case "ru":
|
||||
language = "ru";
|
||||
break;
|
||||
case "vn":
|
||||
language = "vn";
|
||||
case "vi":
|
||||
language = "vi";
|
||||
break;
|
||||
default:
|
||||
language = Conf.DefaultLanguage;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Abmelden",
|
||||
"My Account": "Mein Konto",
|
||||
"Sign Up": "Anmelden"
|
||||
@@ -24,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",
|
||||
@@ -118,13 +120,17 @@
|
||||
"Type - Tooltip": "Art des Zertifikats"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Sub users": "Sub users",
|
||||
"Sub users - Tooltip": "Sub users - Tooltip",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User2": "User2"
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Der Code, den Sie erhalten haben",
|
||||
@@ -160,7 +166,6 @@
|
||||
"Application": "Applikation",
|
||||
"Applications": "Anwendungen",
|
||||
"Applications that require authentication": "Anwendungen, die eine Authentifizierung erfordern",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Avatar": "Avatar",
|
||||
"Avatar - Tooltip": "Öffentliches Avatarbild für den Benutzer",
|
||||
"Back Home": "Zurück nach Hause",
|
||||
@@ -287,8 +292,6 @@
|
||||
"User containers": "Nutzerpools",
|
||||
"User type": "Benutzertyp",
|
||||
"User type - Tooltip": "Tags, denen der Benutzer angehört, standardmäßig auf \"normaler Benutzer\" festgelegt",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users": "Benutzer",
|
||||
"Users under all organizations": "Benutzer unter allen Organisationen",
|
||||
"Webhooks": "Webhooks",
|
||||
@@ -322,7 +325,8 @@
|
||||
"Server port": "Server-Port",
|
||||
"Server port - Tooltip": "LDAP-Server-Port",
|
||||
"The Auto Sync option will sync all users to specify organization": "Die Option \"Auto Sync\" synchronisiert alle Benutzer mit der angegebenen Organisation",
|
||||
"UidNumber / Uid": "UidNumber / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Automatische Anmeldung",
|
||||
@@ -352,6 +356,7 @@
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
@@ -737,18 +742,27 @@
|
||||
"Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname",
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Selbstvorstellung des Nutzers",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Captcha-Überprüfung fehlgeschlagen",
|
||||
"Captcha Verify Success": "Captcha-Verifizierung Erfolgreich",
|
||||
"Country code": "Ländercode",
|
||||
"Country/Region": "Land/Region",
|
||||
"Country/Region - Tooltip": "Land oder Region",
|
||||
"Edit User": "Benutzer bearbeiten",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "E-Mail darf nicht leer sein",
|
||||
"Email/phone reset successfully": "E-Mail-/Telefon-Zurücksetzung erfolgreich durchgeführt",
|
||||
"Empty input!": "Leere Eingabe!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Startseite des Benutzers",
|
||||
"Homepage - Tooltip": "Homepage-URL des Benutzers",
|
||||
"ID card": "Ausweis",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"Input your phone number": "Geben Sie Ihre Telefonnummer ein",
|
||||
"Is admin": "Ist Admin",
|
||||
@@ -759,7 +773,12 @@
|
||||
"Is forbidden - Tooltip": "Verbotene Benutzer können sich nicht mehr einloggen",
|
||||
"Is global admin": "Ist globaler Administrator",
|
||||
"Is global admin - Tooltip": "Ist ein Administrator von Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Keys",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Link",
|
||||
"Location": "Ort",
|
||||
"Location - Tooltip": "Stadt des Wohnsitzes",
|
||||
@@ -775,9 +794,13 @@
|
||||
"Please select avatar from resources": "Bitte wählen Sie einen Avatar aus den Ressourcen aus",
|
||||
"Properties": "Eigenschaften",
|
||||
"Properties - Tooltip": "Eigenschaften des Benutzers",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Neueingabe wiederholen",
|
||||
"Reset Email...": "E-Mail zurücksetzen...",
|
||||
"Reset Phone...": "Telefon zurücksetzen...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Wählen Sie ein Foto aus...",
|
||||
"Set Password": "Passwort festlegen",
|
||||
"Set new profile picture": "Neues Profilbild festlegen",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user