Compare commits

...

9 Commits

Author SHA1 Message Date
91cb5f393a fix: fix Swagger docs page (#2025)
Signed-off-by: baihhh <2542274498@qq.com>
2023-06-30 00:48:39 +08:00
807aea5ec7 feat: add tags to application (#2027)
* feat: add tags to application

* fix: fix for merge master

* feat: update i18n(backend&frontend) for application tags
2023-06-30 00:04:12 +08:00
1c42b6e395 fix: refactor the idp and regex code (#2030)
* refactor: validate util and idp

* chore: clean code

* chore: clean code
2023-06-29 21:44:14 +08:00
49a73f8138 fix: getOrganization without pagination for global admin (#2028)
* fix: getOrganization without pagination for global admin return only built-in org

* fix gofumpt
2023-06-29 18:56:19 +08:00
55784c68a3 Fix bug in /get-organizations API for org admin 2023-06-28 09:19:39 +08:00
8080b10b3b feat: show code signin page with password disabled (#2021) 2023-06-28 00:38:48 +08:00
cd7589775c feat: replace all panic by response err (#1993)
* fix: missing return after response error

* feat: handle error in frontend

* feat: disable loading and catch org edit error

* chore: i18 for error message

* chore: remove break line

* feat: application catching error
2023-06-27 21:33:47 +08:00
0a8c2a35fe feat: add TOTP multi-factor authentication (#2014)
* feat: add totp multi-factor authentication

* feat: add license

* feat:i18n and update yarn.lock

* feat:i18n

* fix: i18n
2023-06-24 18:39:54 +08:00
d1e734e4ce fix: set the default value of user.Groups for syncer (#2016)
fix: set the default value of user.Groups for syncer
2023-06-24 18:29:50 +08:00
94 changed files with 3271 additions and 1912 deletions

View File

@ -50,7 +50,8 @@ func (c *ApiController) GetApplications() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplications(applications, userId) c.Data["json"] = object.GetMaskedApplications(applications, userId)
@ -59,13 +60,15 @@ func (c *ApiController) GetApplications() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetApplicationCount(owner, field, value) count, err := object.GetApplicationCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
applications := object.GetMaskedApplications(app, userId) applications := object.GetMaskedApplications(app, userId)
@ -85,7 +88,8 @@ func (c *ApiController) GetApplication() {
id := c.Input().Get("id") id := c.Input().Get("id")
app, err := object.GetApplication(id) app, err := object.GetApplication(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplication(app, userId) c.Data["json"] = object.GetMaskedApplication(app, userId)
@ -104,7 +108,8 @@ func (c *ApiController) GetUserApplication() {
id := c.Input().Get("id") id := c.Input().Get("id")
user, err := object.GetUser(id) user, err := object.GetUser(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if user == nil { if user == nil {
@ -114,7 +119,8 @@ func (c *ApiController) GetUserApplication() {
app, err := object.GetApplicationByUser(user) app, err := object.GetApplicationByUser(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplication(app, userId) c.Data["json"] = object.GetMaskedApplication(app, userId)
@ -147,7 +153,8 @@ func (c *ApiController) GetOrganizationApplications() {
if limit == "" || page == "" { if limit == "" || page == "" {
applications, err := object.GetOrganizationApplications(owner, organization) applications, err := object.GetOrganizationApplications(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplications(applications, userId) c.Data["json"] = object.GetMaskedApplications(applications, userId)

View File

@ -69,6 +69,15 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
return return
} }
// check user's tag
if !user.IsGlobalAdmin && !user.IsAdmin && len(application.Tags) > 0 {
// only users with the tag that is listed in the application tags can login
if !util.InSlice(application.Tags, user.Tag) {
c.ResponseError(fmt.Sprintf(c.T("auth:User's tag: %s is not listed in the application's tags"), user.Tag))
return
}
}
if form.Password != "" && user.IsMfaEnabled() { if form.Password != "" && user.IsMfaEnabled() {
c.setMfaSessionData(&object.MfaSessionData{UserId: userId}) c.setMfaSessionData(&object.MfaSessionData{UserId: userId})
resp = &Response{Status: object.NextMfa, Data: user.GetPreferredMfaProps(true)} resp = &Response{Status: object.NextMfa, Data: user.GetPreferredMfaProps(true)}
@ -238,7 +247,7 @@ func isProxyProviderType(providerType string) bool {
// @Param code_challenge_method query string false code_challenge_method // @Param code_challenge_method query string false code_challenge_method
// @Param code_challenge query string false code_challenge // @Param code_challenge query string false code_challenge
// @Param form body controllers.AuthForm true "Login information" // @Param form body controllers.AuthForm true "Login information"
// @Success 200 {object} Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /login [post] // @router /login [post]
func (c *ApiController) Login() { func (c *ApiController) Login() {
resp := &Response{} resp := &Response{}
@ -756,7 +765,8 @@ func (c *ApiController) HandleSamlLogin() {
func (c *ApiController) HandleOfficialAccountEvent() { func (c *ApiController) HandleOfficialAccountEvent() {
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body) respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
var data struct { var data struct {
@ -766,7 +776,8 @@ func (c *ApiController) HandleOfficialAccountEvent() {
} }
err = xml.Unmarshal(respBytes, &data) err = xml.Unmarshal(respBytes, &data)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
lock.Lock() lock.Lock()

View File

@ -79,7 +79,8 @@ func (c *ApiController) getCurrentUser() *object.User {
} else { } else {
user, err = object.GetUser(userId) user, err = object.GetUser(userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil
} }
} }
return user return user
@ -112,7 +113,8 @@ func (c *ApiController) GetSessionApplication() *object.Application {
} }
application, err := object.GetApplicationByClientId(clientId.(string)) application, err := object.GetApplicationByClientId(clientId.(string))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil
} }
return application return application

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetCerts() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner)) maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedCerts c.Data["json"] = maskedCerts
@ -50,13 +51,15 @@ func (c *ApiController) GetCerts() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetCertCount(owner, field, value) count, err := object.GetCertCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(certs, paginator.Nums()) c.ResponseOk(certs, paginator.Nums())
@ -80,7 +83,8 @@ func (c *ApiController) GetGlobleCerts() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts()) maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedCerts c.Data["json"] = maskedCerts
@ -89,13 +93,15 @@ func (c *ApiController) GetGlobleCerts() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetGlobalCertsCount(field, value) count, err := object.GetGlobalCertsCount(field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder)) certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(certs, paginator.Nums()) c.ResponseOk(certs, paginator.Nums())
@ -113,7 +119,8 @@ func (c *ApiController) GetCert() {
id := c.Input().Get("id") id := c.Input().Get("id")
cert, err := object.GetCert(id) cert, err := object.GetCert(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedCert(cert) c.Data["json"] = object.GetMaskedCert(cert)

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetChats() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedChats, err := object.GetMaskedChats(object.GetChats(owner)) maskedChats, err := object.GetMaskedChats(object.GetChats(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedChats c.Data["json"] = maskedChats
@ -77,7 +78,8 @@ func (c *ApiController) GetChat() {
maskedChat, err := object.GetMaskedChat(object.GetChat(id)) maskedChat, err := object.GetMaskedChat(object.GetChat(id))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedChat c.Data["json"] = maskedChat

View File

@ -53,7 +53,8 @@ func (c *ApiController) GetMessages() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedMessages(messages) c.Data["json"] = object.GetMaskedMessages(messages)
@ -89,7 +90,8 @@ func (c *ApiController) GetMessage() {
id := c.Input().Get("id") id := c.Input().Get("id")
message, err := object.GetMessage(id) message, err := object.GetMessage(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedMessage(message) c.Data["json"] = object.GetMaskedMessage(message)
@ -100,7 +102,8 @@ func (c *ApiController) ResponseErrorStream(errorText string) {
event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText) event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText)
_, err := c.Ctx.ResponseWriter.Write([]byte(event)) _, err := c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }
@ -196,7 +199,8 @@ func (c *ApiController) GetMessageAnswer() {
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end") event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
_, err = c.Ctx.ResponseWriter.Write([]byte(event)) _, err = c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
answer := stringBuilder.String() answer := stringBuilder.String()
@ -204,7 +208,8 @@ func (c *ApiController) GetMessageAnswer() {
message.Text = answer message.Text = answer
_, err = object.UpdateMessage(message.GetId(), message) _, err = object.UpdateMessage(message.GetId(), message)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }

View File

@ -17,7 +17,6 @@ package controllers
import ( import (
"net/http" "net/http"
"github.com/beego/beego"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -29,7 +28,7 @@ import (
// @param owner form string true "owner of user" // @param owner form string true "owner of user"
// @param name form string true "name of user" // @param name form string true "name of user"
// @param type form string true "MFA auth type" // @param type form string true "MFA auth type"
// @Success 200 {object} The Response object // @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/initiate [post] // @router /mfa/setup/initiate [post]
func (c *ApiController) MfaSetupInitiate() { func (c *ApiController) MfaSetupInitiate() {
owner := c.Ctx.Request.Form.Get("owner") owner := c.Ctx.Request.Form.Get("owner")
@ -58,10 +57,7 @@ func (c *ApiController) MfaSetupInitiate() {
return return
} }
issuer := beego.AppConfig.String("appname") mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
accountName := user.GetId()
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetModels() {
if limit == "" || page == "" { if limit == "" || page == "" {
models, err := object.GetModels(owner) models, err := object.GetModels(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = models c.Data["json"] = models
@ -77,7 +78,8 @@ func (c *ApiController) GetModel() {
model, err := object.GetModel(id) model, err := object.GetModel(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = model c.Data["json"] = model

View File

@ -38,16 +38,25 @@ func (c *ApiController) GetOrganizations() {
sortField := c.Input().Get("sortField") sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
isGlobalAdmin := c.IsGlobalAdmin()
if limit == "" || page == "" { if limit == "" || page == "" {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner)) var maskedOrganizations []*object.Organization
var err error
if isGlobalAdmin {
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
} else {
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
}
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedOrganizations c.Data["json"] = maskedOrganizations
c.ServeJSON() c.ServeJSON()
} else { } else {
isGlobalAdmin := c.IsGlobalAdmin()
if !isGlobalAdmin { if !isGlobalAdmin {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner)) maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
if err != nil { if err != nil {

View File

@ -42,7 +42,8 @@ func (c *ApiController) GetPayments() {
if limit == "" || page == "" { if limit == "" || page == "" {
payments, err := object.GetPayments(owner) payments, err := object.GetPayments(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = payments c.Data["json"] = payments
@ -51,13 +52,15 @@ func (c *ApiController) GetPayments() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetPaymentCount(owner, organization, field, value) count, err := object.GetPaymentCount(owner, organization, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder) payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(payments, paginator.Nums()) c.ResponseOk(payments, paginator.Nums())
@ -99,7 +102,8 @@ func (c *ApiController) GetPayment() {
payment, err := object.GetPayment(id) payment, err := object.GetPayment(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = payment c.Data["json"] = payment
@ -190,7 +194,8 @@ func (c *ApiController) NotifyPayment() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetPermissions() {
if limit == "" || page == "" { if limit == "" || page == "" {
permissions, err := object.GetPermissions(owner) permissions, err := object.GetPermissions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = permissions c.Data["json"] = permissions
@ -50,13 +51,15 @@ func (c *ApiController) GetPermissions() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetPermissionCount(owner, field, value) count, err := object.GetPermissionCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(permissions, paginator.Nums()) c.ResponseOk(permissions, paginator.Nums())
@ -116,7 +119,8 @@ func (c *ApiController) GetPermission() {
permission, err := object.GetPermission(id) permission, err := object.GetPermission(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = permission c.Data["json"] = permission

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetPlans() {
if limit == "" || page == "" { if limit == "" || page == "" {
plans, err := object.GetPlans(owner) plans, err := object.GetPlans(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = plans c.Data["json"] = plans
@ -79,13 +80,15 @@ func (c *ApiController) GetPlan() {
plan, err := object.GetPlan(id) plan, err := object.GetPlan(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if includeOption { if includeOption {
options, err := object.GetPermissionsByRole(plan.Role) options, err := object.GetPermissionsByRole(plan.Role)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
for _, option := range options { for _, option := range options {

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetPricings() {
if limit == "" || page == "" { if limit == "" || page == "" {
pricings, err := object.GetPricings(owner) pricings, err := object.GetPricings(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = pricings c.Data["json"] = pricings
@ -70,14 +71,15 @@ func (c *ApiController) GetPricings() {
// @Tag Pricing API // @Tag Pricing API
// @Description get pricing // @Description get pricing
// @Param id query string true "The id ( owner/name ) of the pricing" // @Param id query string true "The id ( owner/name ) of the pricing"
// @Success 200 {object} object.pricing The Response object // @Success 200 {object} object.Pricing The Response object
// @router /get-pricing [get] // @router /get-pricing [get]
func (c *ApiController) GetPricing() { func (c *ApiController) GetPricing() {
id := c.Input().Get("id") id := c.Input().Get("id")
pricing, err := object.GetPricing(id) pricing, err := object.GetPricing(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = pricing c.Data["json"] = pricing

View File

@ -42,7 +42,8 @@ func (c *ApiController) GetProducts() {
if limit == "" || page == "" { if limit == "" || page == "" {
products, err := object.GetProducts(owner) products, err := object.GetProducts(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = products c.Data["json"] = products
@ -78,12 +79,14 @@ func (c *ApiController) GetProduct() {
product, err := object.GetProduct(id) product, err := object.GetProduct(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
err = object.ExtendProductWithProviders(product) err = object.ExtendProductWithProviders(product)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = product c.Data["json"] = product

View File

@ -46,7 +46,8 @@ func (c *ApiController) GetProviders() {
if limit == "" || page == "" { if limit == "" || page == "" {
providers, err := object.GetProviders(owner) providers, err := object.GetProviders(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled)) c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
@ -92,7 +93,8 @@ func (c *ApiController) GetGlobalProviders() {
if limit == "" || page == "" { if limit == "" || page == "" {
globalProviders, err := object.GetGlobalProviders() globalProviders, err := object.GetGlobalProviders()
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled)) c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))

View File

@ -46,7 +46,8 @@ func (c *ApiController) GetRecords() {
if limit == "" || page == "" { if limit == "" || page == "" {
records, err := object.GetRecords() records, err := object.GetRecords()
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = records c.Data["json"] = records
@ -84,12 +85,14 @@ func (c *ApiController) GetRecordsByFilter() {
record := &object.Record{} record := &object.Record{}
err := util.JsonToStruct(body, record) err := util.JsonToStruct(body, record)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
records, err := object.GetRecordsByField(record) records, err := object.GetRecordsByField(record)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = records c.Data["json"] = records

View File

@ -53,7 +53,8 @@ func (c *ApiController) GetResources() {
if limit == "" || page == "" { if limit == "" || page == "" {
resources, err := object.GetResources(owner, user) resources, err := object.GetResources(owner, user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resources c.Data["json"] = resources
@ -86,7 +87,8 @@ func (c *ApiController) GetResource() {
resource, err := object.GetResource(id) resource, err := object.GetResource(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resource c.Data["json"] = resource

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetRoles() {
if limit == "" || page == "" { if limit == "" || page == "" {
roles, err := object.GetRoles(owner) roles, err := object.GetRoles(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = roles c.Data["json"] = roles
@ -77,7 +78,8 @@ func (c *ApiController) GetRole() {
role, err := object.GetRole(id) role, err := object.GetRole(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = role c.Data["json"] = role

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetSessions() {
if limit == "" || page == "" { if limit == "" || page == "" {
sessions, err := object.GetSessions(owner) sessions, err := object.GetSessions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = sessions c.Data["json"] = sessions
@ -76,7 +77,8 @@ func (c *ApiController) GetSingleSession() {
session, err := object.GetSingleSession(id) session, err := object.GetSingleSession(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = session c.Data["json"] = session
@ -155,7 +157,8 @@ func (c *ApiController) IsSessionDuplicated() {
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId) isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated} c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetSubscriptions() {
if limit == "" || page == "" { if limit == "" || page == "" {
subscriptions, err := object.GetSubscriptions(owner) subscriptions, err := object.GetSubscriptions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = subscriptions c.Data["json"] = subscriptions
@ -70,14 +71,15 @@ func (c *ApiController) GetSubscriptions() {
// @Tag Subscription API // @Tag Subscription API
// @Description get subscription // @Description get subscription
// @Param id query string true "The id ( owner/name ) of the subscription" // @Param id query string true "The id ( owner/name ) of the subscription"
// @Success 200 {object} object.subscription The Response object // @Success 200 {object} object.Subscription The Response object
// @router /get-subscription [get] // @router /get-subscription [get]
func (c *ApiController) GetSubscription() { func (c *ApiController) GetSubscription() {
id := c.Input().Get("id") id := c.Input().Get("id")
subscription, err := object.GetSubscription(id) subscription, err := object.GetSubscription(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = subscription c.Data["json"] = subscription

View File

@ -42,7 +42,8 @@ func (c *ApiController) GetSyncers() {
if limit == "" || page == "" { if limit == "" || page == "" {
organizationSyncers, err := object.GetOrganizationSyncers(owner, organization) organizationSyncers, err := object.GetOrganizationSyncers(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = organizationSyncers c.Data["json"] = organizationSyncers
@ -78,7 +79,8 @@ func (c *ApiController) GetSyncer() {
syncer, err := object.GetSyncer(id) syncer, err := object.GetSyncer(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = syncer c.Data["json"] = syncer

View File

@ -43,7 +43,8 @@ func (c *ApiController) GetTokens() {
if limit == "" || page == "" { if limit == "" || page == "" {
token, err := object.GetTokens(owner, organization) token, err := object.GetTokens(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = token c.Data["json"] = token
@ -78,7 +79,8 @@ func (c *ApiController) GetToken() {
id := c.Input().Get("id") id := c.Input().Get("id")
token, err := object.GetToken(id) token, err := object.GetToken(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = token c.Data["json"] = token
@ -193,7 +195,8 @@ func (c *ApiController) GetOAuthToken() {
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage()) oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = oAuthtoken c.Data["json"] = oAuthtoken
@ -236,7 +239,8 @@ func (c *ApiController) RefreshToken() {
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host) refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = refreshToken2 c.Data["json"] = refreshToken2
@ -276,7 +280,8 @@ func (c *ApiController) IntrospectToken() {
} }
application, err := object.GetApplicationByClientId(clientId) application, err := object.GetApplicationByClientId(clientId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if application == nil || application.ClientSecret != clientSecret { if application == nil || application.ClientSecret != clientSecret {
@ -289,7 +294,8 @@ func (c *ApiController) IntrospectToken() {
} }
token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name) token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if token == nil { if token == nil {

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetGlobalUsers() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers()) maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers
@ -101,7 +102,8 @@ func (c *ApiController) GetUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner)) maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers
@ -153,7 +155,8 @@ func (c *ApiController) GetUser() {
if userId != "" && owner != "" { if userId != "" && owner != "" {
userFromUserId, err = object.GetUserByUserId(owner, userId) userFromUserId, err = object.GetUserByUserId(owner, userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
id = util.GetId(userFromUserId.Owner, userFromUserId.Name) id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
@ -165,7 +168,8 @@ func (c *ApiController) GetUser() {
organization, err := object.GetOrganization(util.GetId("admin", owner)) organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if !organization.IsProfilePublic { if !organization.IsProfilePublic {
@ -190,18 +194,21 @@ func (c *ApiController) GetUser() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
user.MultiFactorAuths = object.GetAllMfaProps(user, true) user.MultiFactorAuths = object.GetAllMfaProps(user, true)
err = object.ExtendUserWithRolesAndPermissions(user) err = object.ExtendUserWithRolesAndPermissions(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
maskedUser, err := object.GetMaskedUser(user) maskedUser, err := object.GetMaskedUser(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUser c.Data["json"] = maskedUser
@ -498,7 +505,8 @@ func (c *ApiController) GetSortedUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit)) maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers

View File

@ -97,7 +97,8 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user, err := object.GetUser(userId) user, err := object.GetUser(userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil, false
} }
if user == nil { if user == nil {

View File

@ -66,7 +66,7 @@ func (c *ApiController) WebAuthnSignupBegin() {
// @Tag User API // @Tag User API
// @Description WebAuthn Registration Flow 2nd stage // @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response" // @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object" // @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signup/finish [post] // @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() { func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host) webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
@ -150,7 +150,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @Tag Login API // @Tag Login API
// @Description WebAuthn Login Flow 2nd stage // @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response" // @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object" // @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signin/finish [post] // @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() { func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")

View File

@ -42,7 +42,8 @@ func (c *ApiController) GetWebhooks() {
if limit == "" || page == "" { if limit == "" || page == "" {
webhooks, err := object.GetWebhooks(owner, organization) webhooks, err := object.GetWebhooks(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = webhooks c.Data["json"] = webhooks
@ -79,7 +80,8 @@ func (c *ApiController) GetWebhook() {
webhook, err := object.GetWebhook(id) webhook, err := object.GetWebhook(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = webhook c.Data["json"] = webhook

6
go.mod
View File

@ -42,14 +42,14 @@ require (
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5 github.com/nyaruka/phonenumbers v1.1.5
github.com/pkoukk/tiktoken-go v0.1.1 github.com/pkoukk/tiktoken-go v0.1.1
github.com/plutov/paypal/v4 v4.7.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.2.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0 github.com/russellhaering/goxmldsig v1.2.0
github.com/sashabaranov/go-openai v1.9.1 github.com/sashabaranov/go-openai v1.12.0
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
@ -59,7 +59,7 @@ require (
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/xorm-io/builder v0.3.13 // indirect github.com/xorm-io/builder v0.3.13
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect

9
go.sum
View File

@ -105,6 +105,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
@ -495,10 +497,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo= github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw= github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw=
github.com/plutov/paypal/v4 v4.7.0 h1:6TRvYD4ny6yQfHaABeStNf43GFM1wpW5jU/XEDGQmq0=
github.com/plutov/paypal/v4 v4.7.0/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -546,6 +548,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM= github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.12.0 h1:aRNHH0gtVfrpIaEolD0sWrLLRnYQNK4cH/bIAHwL8Rk=
github.com/sashabaranov/go-openai v1.12.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@ -595,7 +599,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert", "The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert",
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert", "The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
"Unauthorized operation": "Nicht autorisierte Operation", "Unauthorized operation": "Nicht autorisierte Operation",
"Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s" "Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein" "Service %s and %s do not match": "Service %s und %s stimmen nicht überein"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application", "The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s" "Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación", "The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación",
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación", "The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
"Unauthorized operation": "Operación no autorizada", "Unauthorized operation": "Operación no autorizada",
"Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %s" "Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden" "Service %s and %s do not match": "Los servicios %s y %s no coinciden"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application", "The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application",
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application", "The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
"Unauthorized operation": "Opération non autorisée", "Unauthorized operation": "Opération non autorisée",
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s" "Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas" "Service %s and %s do not match": "Les services %s et %s ne correspondent pas"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut", "The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini", "The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
"Unauthorized operation": "Operasi tidak sah", "Unauthorized operation": "Operasi tidak sah",
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s" "Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok" "Service %s and %s do not match": "Layanan %s dan %s tidak cocok"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません", "The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません",
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません", "The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
"Unauthorized operation": "不正操作", "Unauthorized operation": "不正操作",
"Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%s" "Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "サービス%sと%sは一致しません" "Service %s and %s do not match": "サービス%sと%sは一致しません"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다", "The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다",
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다", "The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
"Unauthorized operation": "무단 조작", "Unauthorized operation": "무단 조작",
"Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %s" "Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다" "Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application", "The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s" "Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения", "The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения", "The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
"Unauthorized operation": "Несанкционированная операция", "Unauthorized operation": "Несанкционированная операция",
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s" "Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Сервисы %s и %s не совпадают" "Service %s and %s do not match": "Сервисы %s и %s не совпадают"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng", "The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng",
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng", "The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
"Unauthorized operation": "Hoạt động không được ủy quyền", "Unauthorized operation": "Hoạt động không được ủy quyền",
"Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %s" "Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp" "Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式", "The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式",
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用", "The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s" "Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s",
"User's tag: %s is not listed in the application's tags": "用户的标签: %s不在该应用的标签列表中"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "服务%s与%s不匹配" "Service %s and %s do not match": "服务%s与%s不匹配"

View File

@ -198,12 +198,22 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) { func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret) accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
request, err := http.NewRequest("GET", accessTokenUrl, nil) request, err := http.NewRequest("GET", accessTokenUrl, nil)
if err != nil {
return "", err
}
client := new(http.Client) client := new(http.Client)
resp, err := client.Do(request) resp, err := client.Do(request)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body) respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err
} }
var data struct { var data struct {
ExpireIn int `json:"expires_in"` ExpireIn int `json:"expires_in"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
@ -212,20 +222,30 @@ func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (
if err != nil { if err != nil {
return "", err return "", err
} }
return data.AccessToken, nil return data.AccessToken, nil
} }
func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) { func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) {
accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret) accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret)
client := new(http.Client) client := new(http.Client)
params := "{\"action_name\": \"QR_LIMIT_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}"
weChatEndpoint := "https://api.weixin.qq.com/cgi-bin/qrcode/create"
qrCodeUrl := fmt.Sprintf("%s?access_token=%s", weChatEndpoint, accessToken)
params := `{"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "test"}}}`
bodyData := bytes.NewReader([]byte(params)) bodyData := bytes.NewReader([]byte(params))
qrCodeUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", accessToken)
requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData) requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData)
if err != nil {
return "", err
}
resp, err := client.Do(requeset) resp, err := client.Do(requeset)
if err != nil { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body) respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -275,7 +275,7 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
session = session.And("owner=?", owner) session = session.And("owner=?", owner)
} }
if field != "" && value != "" { if field != "" && value != "" {
if filterField(field) { if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
} }
} }
@ -303,7 +303,7 @@ func GetSessionForUser(owner string, offset, limit int, field, value, sortField,
} }
} }
if field != "" && value != "" { if field != "" && value != "" {
if filterField(field) { if util.FilterField(field) {
if offset != -1 { if offset != -1 {
field = fmt.Sprintf("a.%s", field) field = fmt.Sprintf("a.%s", field)
} }

View File

@ -57,6 +57,7 @@ type Application struct {
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
Tags []string `xorm:"mediumtext" json:"tags"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@ -28,21 +27,11 @@ import (
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
) )
var (
reWhiteSpace *regexp.Regexp
reFieldWhiteList *regexp.Regexp
)
const ( const (
SigninWrongTimesLimit = 5 SigninWrongTimesLimit = 5
LastSignWrongTimeDuration = time.Minute * 15 LastSignWrongTimeDuration = time.Minute * 15
) )
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
}
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string { func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
if organization == nil { if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist") return i18n.Translate(lang, "check:Organization does not exist")
@ -58,7 +47,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
if util.IsEmailValid(form.Username) { if util.IsEmailValid(form.Username) {
return i18n.Translate(lang, "check:Username cannot be an email address") return i18n.Translate(lang, "check:Username cannot be an email address")
} }
if reWhiteSpace.MatchString(form.Username) { if util.ReWhiteSpace.MatchString(form.Username) {
return i18n.Translate(lang, "check:Username cannot contain white spaces") return i18n.Translate(lang, "check:Username cannot contain white spaces")
} }
@ -294,10 +283,6 @@ func CheckUserPassword(organization string, username string, password string, la
return user, "" return user, ""
} }
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) { func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
if requestUserId == "" { if requestUserId == "" {
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first")) return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
@ -397,8 +382,8 @@ func CheckUsername(username string, lang string) string {
} }
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex // https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(username) { if !util.ReUserName.MatchString(username) {
return i18n.Translate(lang, "check:The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.") return i18n.Translate(lang, "check:The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.")
} }

View File

@ -184,6 +184,7 @@ func initBuiltInApplication() {
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
}, },
Tags: []string{},
RedirectUris: []string{}, RedirectUris: []string{},
ExpireInHours: 168, ExpireInHours: 168,
FormOffset: 2, FormOffset: 2,

View File

@ -145,6 +145,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
if application.RedirectUris == nil { if application.RedirectUris == nil {
application.RedirectUris = []string{} application.RedirectUris = []string{}
} }
if application.Tags == nil {
application.Tags = []string{}
}
} }
for _, permission := range data.Permissions { for _, permission := range data.Permissions {
if permission.Actions == nil { if permission.Actions == nil {

View File

@ -22,6 +22,8 @@ import (
"github.com/beego/beego/context" "github.com/beego/beego/context"
) )
const MfaRecoveryCodesSession = "mfa_recovery_codes"
type MfaSessionData struct { type MfaSessionData struct {
UserId string UserId string
} }
@ -37,10 +39,10 @@ type MfaProps struct {
} }
type MfaInterface interface { type MfaInterface interface {
SetupVerify(ctx *context.Context, passCode string) error Initiate(ctx *context.Context, userId string) (*MfaProps, error)
Verify(passCode string) error SetupVerify(ctx *context.Context, passcode string) error
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
Enable(ctx *context.Context, user *User) error Enable(ctx *context.Context, user *User) error
Verify(passcode string) error
} }
const ( const (
@ -58,11 +60,11 @@ const (
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface { func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
switch mfaType { switch mfaType {
case SmsType: case SmsType:
return NewSmsTwoFactor(config) return NewSmsMfaUtil(config)
case EmailType: case EmailType:
return NewEmailTwoFactor(config) return NewEmailMfaUtil(config)
case TotpType: case TotpType:
return nil return NewTotpMfaUtil(config)
} }
return nil return nil
@ -97,23 +99,9 @@ func MfaRecover(user *User, recoveryCode string) error {
func GetAllMfaProps(user *User, masked bool) []*MfaProps { func GetAllMfaProps(user *User, masked bool) []*MfaProps {
mfaProps := []*MfaProps{} mfaProps := []*MfaProps{}
if user.MfaPhoneEnabled { for _, mfaType := range []string{SmsType, EmailType, TotpType} {
mfaProps = append(mfaProps, user.GetMfaProps(SmsType, masked)) mfaProps = append(mfaProps, user.GetMfaProps(mfaType, masked))
} else {
mfaProps = append(mfaProps, &MfaProps{
Enabled: false,
MfaType: SmsType,
})
} }
if user.MfaEmailEnabled {
mfaProps = append(mfaProps, user.GetMfaProps(EmailType, masked))
} else {
mfaProps = append(mfaProps, &MfaProps{
Enabled: false,
MfaType: EmailType,
})
}
return mfaProps return mfaProps
} }
@ -121,6 +109,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps := &MfaProps{} mfaProps := &MfaProps{}
if mfaType == SmsType { if mfaType == SmsType {
if !user.MfaPhoneEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: user.MfaPhoneEnabled, Enabled: user.MfaPhoneEnabled,
MfaType: mfaType, MfaType: mfaType,
@ -132,6 +127,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps.Secret = user.Phone mfaProps.Secret = user.Phone
} }
} else if mfaType == EmailType { } else if mfaType == EmailType {
if !user.MfaEmailEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: user.MfaEmailEnabled, Enabled: user.MfaEmailEnabled,
MfaType: mfaType, MfaType: mfaType,
@ -142,9 +144,22 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps.Secret = user.Email mfaProps.Secret = user.Email
} }
} else if mfaType == TotpType { } else if mfaType == TotpType {
if user.TotpSecret == "" {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: true,
MfaType: mfaType, MfaType: mfaType,
} }
if masked {
mfaProps.Secret = ""
} else {
mfaProps.Secret = user.TotpSecret
}
} }
if user.PreferredMfaType == mfaType { if user.PreferredMfaType == mfaType {
@ -158,8 +173,9 @@ func DisabledMultiFactorAuth(user *User) error {
user.RecoveryCodes = []string{} user.RecoveryCodes = []string{}
user.MfaPhoneEnabled = false user.MfaPhoneEnabled = false
user.MfaEmailEnabled = false user.MfaEmailEnabled = false
user.TotpSecret = ""
_, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled"}, user.IsAdminUser()) _, err := updateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled", "totp_secret"})
if err != nil { if err != nil {
return err return err
} }

View File

@ -18,26 +18,24 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/casdoor/casdoor/util"
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid" "github.com/google/uuid"
) )
const ( const (
MfaSmsCountryCodeSession = "mfa_country_code" MfaSmsCountryCodeSession = "mfa_country_code"
MfaSmsDestSession = "mfa_dest" MfaSmsDestSession = "mfa_dest"
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
) )
type SmsMfa struct { type SmsMfa struct {
Config *MfaProps Config *MfaProps
} }
func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*MfaProps, error) { func (mfa *SmsMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
recoveryCode := uuid.NewString() recoveryCode := uuid.NewString()
err := ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode}) err := ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -63,9 +61,9 @@ func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
} }
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error { func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string) recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
if len(recoveryCodes) == 0 { if len(recoveryCodes) == 0 {
return fmt.Errorf("recovery codes is empty") return fmt.Errorf("recovery codes is missing")
} }
columns := []string{"recovery_codes", "preferred_mfa_type"} columns := []string{"recovery_codes", "preferred_mfa_type"}
@ -111,7 +109,7 @@ func (mfa *SmsMfa) Verify(passCode string) error {
return nil return nil
} }
func NewSmsTwoFactor(config *MfaProps) *SmsMfa { func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
if config == nil { if config == nil {
config = &MfaProps{ config = &MfaProps{
MfaType: SmsType, MfaType: SmsType,
@ -122,7 +120,7 @@ func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
} }
} }
func NewEmailTwoFactor(config *MfaProps) *SmsMfa { func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
if config == nil { if config == nil {
config = &MfaProps{ config = &MfaProps{
MfaType: EmailType, MfaType: EmailType,

133
object/mfa_totp.go Normal file
View File

@ -0,0 +1,133 @@
// 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 (
"errors"
"fmt"
"github.com/beego/beego"
"github.com/beego/beego/context"
"github.com/google/uuid"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
const MfaTotpSecretSession = "mfa_totp_secret"
type TotpMfa struct {
Config *MfaProps
period uint
secretSize uint
digits otp.Digits
}
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
issuer := beego.AppConfig.String("appname")
if issuer == "" {
issuer = "casdoor"
}
key, err := totp.Generate(totp.GenerateOpts{
Issuer: issuer,
AccountName: userId,
Period: mfa.period,
SecretSize: mfa.secretSize,
Digits: mfa.digits,
})
if err != nil {
return nil, err
}
err = ctx.Input.CruSession.Set(MfaTotpSecretSession, key.Secret())
if err != nil {
return nil, err
}
recoveryCode := uuid.NewString()
err = ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
if err != nil {
return nil, err
}
mfaProps := MfaProps{
MfaType: mfa.Config.MfaType,
RecoveryCodes: []string{recoveryCode},
Secret: key.Secret(),
URL: key.URL(),
}
return &mfaProps, nil
}
func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
result := totp.Validate(passcode, secret)
if result {
return nil
} else {
return errors.New("totp passcode error")
}
}
func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
if len(recoveryCodes) == 0 {
return fmt.Errorf("recovery codes is missing")
}
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
if secret == "" {
return fmt.Errorf("totp secret is missing")
}
columns := []string{"recovery_codes", "preferred_mfa_type", "totp_secret"}
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
user.TotpSecret = secret
if user.PreferredMfaType == "" {
user.PreferredMfaType = mfa.Config.MfaType
}
_, err := updateUser(user.GetId(), user, columns)
if err != nil {
return err
}
return nil
}
func (mfa *TotpMfa) Verify(passcode string) error {
result := totp.Validate(passcode, mfa.Config.Secret)
if result {
return nil
} else {
return errors.New("totp passcode error")
}
}
func NewTotpMfaUtil(config *MfaProps) *TotpMfa {
if config == nil {
config = &MfaProps{
MfaType: TotpType,
}
}
return &TotpMfa{
Config: config,
period: 30,
secretSize: 20,
digits: otp.DigitsSix,
}
}

View File

@ -170,6 +170,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
originalUser := &OriginalUser{ originalUser := &OriginalUser{
Address: []string{}, Address: []string{},
Properties: map[string]string{}, Properties: map[string]string{},
Groups: []string{},
} }
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {

View File

@ -161,6 +161,7 @@ type User struct {
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"` PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"` RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"`
TotpSecret string `xorm:"varchar(100)" json:"totpSecret,omitempty"`
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"` MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
MfaEmailEnabled bool `json:"mfaEmailEnabled"` MfaEmailEnabled bool `json:"mfaEmailEnabled"`
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"` MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
@ -432,16 +433,19 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
if user.AccessSecret != "" { if user.AccessSecret != "" {
user.AccessSecret = "***" user.AccessSecret = "***"
} }
if user.RecoveryCodes != nil {
user.RecoveryCodes = nil
}
if user.ManagedAccounts != nil { if user.ManagedAccounts != nil {
for _, manageAccount := range user.ManagedAccounts { for _, manageAccount := range user.ManagedAccounts {
manageAccount.Password = "***" manageAccount.Password = "***"
} }
} }
if user.TotpSecret != "" {
user.TotpSecret = ""
}
if user.RecoveryCodes != nil {
user.RecoveryCodes = nil
}
return user, nil return user, nil
} }

View File

@ -43,6 +43,34 @@
} }
} }
}, },
"/api/add-adapter": {
"post": {
"tags": [
"Adapter API"
],
"description": "add adapter",
"operationId": "ApiController.AddCasbinAdapter",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the adapter",
"required": true,
"schema": {
"$ref": "#/definitions/object.Adapter"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-application": { "/api/add-application": {
"post": { "post": {
"tags": [ "tags": [
@ -127,6 +155,34 @@
} }
} }
}, },
"/api/add-group": {
"post": {
"tags": [
"Group API"
],
"description": "add group",
"operationId": "ApiController.AddGroup",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the group",
"required": true,
"schema": {
"$ref": "#/definitions/object.Group"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-ldap": { "/api/add-ldap": {
"post": { "post": {
"tags": [ "tags": [
@ -599,6 +655,14 @@
} }
} }
}, },
"/api/add-user-keys": {
"post": {
"tags": [
"User API"
],
"operationId": "ApiController.AddUserkeys"
}
},
"/api/add-webhook": { "/api/add-webhook": {
"post": { "post": {
"tags": [ "tags": [
@ -782,13 +846,13 @@
"tags": [ "tags": [
"Enforce API" "Enforce API"
], ],
"description": "perform enforce", "description": "Call Casbin BatchEnforce API",
"operationId": "ApiController.BatchEnforce", "operationId": "ApiController.BatchEnforce",
"parameters": [ "parameters": [
{ {
"in": "body", "in": "body",
"name": "body", "name": "body",
"description": "casbin request array", "description": "array of casbin requests",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/object.CasbinRequest" "$ref": "#/definitions/object.CasbinRequest"
@ -858,6 +922,34 @@
"operationId": "ApiController.CheckUserPassword" "operationId": "ApiController.CheckUserPassword"
} }
}, },
"/api/delete-adapter": {
"post": {
"tags": [
"Adapter API"
],
"description": "delete adapter",
"operationId": "ApiController.DeleteCasbinAdapter",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the adapter",
"required": true,
"schema": {
"$ref": "#/definitions/object.Adapter"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-application": { "/api/delete-application": {
"post": { "post": {
"tags": [ "tags": [
@ -942,6 +1034,34 @@
} }
} }
}, },
"/api/delete-group": {
"post": {
"tags": [
"Group API"
],
"description": "delete group",
"operationId": "ApiController.DeleteGroup",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the group",
"required": true,
"schema": {
"$ref": "#/definitions/object.Group"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/delete-ldap": { "/api/delete-ldap": {
"post": { "post": {
"tags": [ "tags": [
@ -1429,13 +1549,13 @@
"tags": [ "tags": [
"Enforce API" "Enforce API"
], ],
"description": "perform enforce", "description": "Call Casbin Enforce API",
"operationId": "ApiController.Enforce", "operationId": "ApiController.Enforce",
"parameters": [ "parameters": [
{ {
"in": "body", "in": "body",
"name": "body", "name": "body",
"description": "casbin request", "description": "Casbin request",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/object.CasbinRequest" "$ref": "#/definitions/object.CasbinRequest"
@ -1487,6 +1607,61 @@
} }
} }
}, },
"/api/get-adapter": {
"get": {
"tags": [
"Adapter API"
],
"description": "get adapter",
"operationId": "ApiController.GetCasbinAdapter",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the adapter",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Adapter"
}
}
}
}
},
"/api/get-adapters": {
"get": {
"tags": [
"Adapter API"
],
"description": "get adapters",
"operationId": "ApiController.GetCasbinAdapters",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of adapters",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Adapter"
}
}
}
}
}
},
"/api/get-app-login": { "/api/get-app-login": {
"get": { "get": {
"tags": [ "tags": [
@ -1825,6 +2000,61 @@
} }
} }
}, },
"/api/get-group": {
"get": {
"tags": [
"Group API"
],
"description": "get group",
"operationId": "ApiController.GetGroup",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the group",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.Group"
}
}
}
}
},
"/api/get-groups": {
"get": {
"tags": [
"Group API"
],
"description": "get groups",
"operationId": "ApiController.GetGroups",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of groups",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Group"
}
}
}
}
}
},
"/api/get-ldap": { "/api/get-ldap": {
"get": { "get": {
"tags": [ "tags": [
@ -2045,7 +2275,7 @@
"tags": [ "tags": [
"Organization API" "Organization API"
], ],
"description": "get all organization names", "description": "get all organization name and displayName",
"operationId": "ApiController.GetOrganizationNames", "operationId": "ApiController.GetOrganizationNames",
"parameters": [ "parameters": [
{ {
@ -2338,7 +2568,7 @@
"200": { "200": {
"description": "The Response object", "description": "The Response object",
"schema": { "schema": {
"$ref": "#/definitions/object.pricing" "$ref": "#/definitions/object.Pricing"
} }
} }
} }
@ -2753,7 +2983,7 @@
"200": { "200": {
"description": "The Response object", "description": "The Response object",
"schema": { "schema": {
"$ref": "#/definitions/object.subscription" "$ref": "#/definitions/object.Subscription"
} }
} }
} }
@ -3328,7 +3558,7 @@
"200": { "200": {
"description": "The Response object", "description": "The Response object",
"schema": { "schema": {
"$ref": "#/definitions/Response" "$ref": "#/definitions/controllers.Response"
} }
} }
} }
@ -3598,9 +3828,9 @@
"operationId": "ApiController.MfaSetupInitiate", "operationId": "ApiController.MfaSetupInitiate",
"responses": { "responses": {
"200": { "200": {
"description": "Response object", "description": "The Response object",
"schema": { "schema": {
"$ref": "#/definitions/The" "$ref": "#/definitions/controllers.Response"
} }
} }
} }
@ -3799,6 +4029,41 @@
] ]
} }
}, },
"/api/update-adapter": {
"post": {
"tags": [
"Adapter API"
],
"description": "update adapter",
"operationId": "ApiController.UpdateCasbinAdapter",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the adapter",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the adapter",
"required": true,
"schema": {
"$ref": "#/definitions/object.Adapter"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-application": { "/api/update-application": {
"post": { "post": {
"tags": [ "tags": [
@ -3904,6 +4169,41 @@
} }
} }
}, },
"/api/update-group": {
"post": {
"tags": [
"Group API"
],
"description": "update group",
"operationId": "ApiController.UpdateGroup",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id ( owner/name ) of the group",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The details of the group",
"required": true,
"schema": {
"$ref": "#/definitions/object.Group"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/update-ldap": { "/api/update-ldap": {
"post": { "post": {
"tags": [ "tags": [
@ -4579,7 +4879,7 @@
"200": { "200": {
"description": "\"The Response object\"", "description": "\"The Response object\"",
"schema": { "schema": {
"$ref": "#/definitions/Response" "$ref": "#/definitions/controllers.Response"
} }
} }
} }
@ -4624,7 +4924,7 @@
"200": { "200": {
"description": "\"The Response object\"", "description": "\"The Response object\"",
"schema": { "schema": {
"$ref": "#/definitions/Response" "$ref": "#/definitions/controllers.Response"
} }
} }
} }
@ -4632,14 +4932,6 @@
} }
}, },
"definitions": { "definitions": {
"1225.0xc0002e2ae0.false": {
"title": "false",
"type": "object"
},
"1260.0xc0002e2b10.false": {
"title": "false",
"type": "object"
},
"LaravelResponse": { "LaravelResponse": {
"title": "LaravelResponse", "title": "LaravelResponse",
"type": "object" "type": "object"
@ -4648,10 +4940,6 @@
"title": "Response", "title": "Response",
"type": "object" "type": "object"
}, },
"The": {
"title": "The",
"type": "object"
},
"controllers.AuthForm": { "controllers.AuthForm": {
"title": "AuthForm", "title": "AuthForm",
"type": "object" "type": "object"
@ -4685,10 +4973,16 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/1225.0xc0002e2ae0.false" "additionalProperties": {
"description": "support string | class | List\u003cclass\u003e and os on",
"type": "string"
}
}, },
"data2": { "data2": {
"$ref": "#/definitions/1260.0xc0002e2b10.false" "additionalProperties": {
"description": "support string | class | List\u003cclass\u003e and os on",
"type": "string"
}
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -4726,8 +5020,8 @@
"title": "JSONWebKey", "title": "JSONWebKey",
"type": "object" "type": "object"
}, },
"object.\u0026{179844 0xc000a02f90 false}": { "object": {
"title": "\u0026{179844 0xc000a02f90 false}", "title": "object",
"type": "object" "type": "object"
}, },
"object.AccountItem": { "object.AccountItem": {
@ -4917,7 +5211,7 @@
"title": "CasbinRequest", "title": "CasbinRequest",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/object.\u0026{179844 0xc000a02f90 false}" "$ref": "#/definitions/object.CasbinRequest"
} }
}, },
"object.Cert": { "object.Cert": {
@ -5029,6 +5323,63 @@
} }
} }
}, },
"object.Group": {
"title": "Group",
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Group"
}
},
"contactEmail": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"displayName": {
"type": "string"
},
"isEnabled": {
"type": "boolean"
},
"isTopGroup": {
"type": "boolean"
},
"key": {
"type": "string"
},
"manager": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"parentId": {
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"type": "string"
},
"updatedTime": {
"type": "string"
},
"users": {
"type": "array",
"items": {
"$ref": "#/definitions/object.User"
}
}
}
},
"object.Header": { "object.Header": {
"title": "Header", "title": "Header",
"type": "object", "type": "object",
@ -5175,12 +5526,15 @@
"countryCode": { "countryCode": {
"type": "string" "type": "string"
}, },
"id": { "enabled": {
"type": "string" "type": "boolean"
}, },
"isPreferred": { "isPreferred": {
"type": "boolean" "type": "boolean"
}, },
"mfaType": {
"type": "string"
},
"recoveryCodes": { "recoveryCodes": {
"type": "array", "type": "array",
"items": { "items": {
@ -5190,9 +5544,6 @@
"secret": { "secret": {
"type": "string" "type": "string"
}, },
"type": {
"type": "string"
},
"url": { "url": {
"type": "string" "type": "string"
} }
@ -5205,6 +5556,9 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"description": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
@ -5362,6 +5716,12 @@
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"passwordOptions": {
"type": "array",
"items": {
"type": "string"
}
},
"passwordSalt": { "passwordSalt": {
"type": "string" "type": "string"
}, },
@ -5492,6 +5852,9 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"description": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
@ -5611,9 +5974,6 @@
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"hasTrial": {
"type": "boolean"
},
"isEnabled": { "isEnabled": {
"type": "boolean" "type": "boolean"
}, },
@ -5933,6 +6293,9 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"description": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
@ -6068,6 +6431,9 @@
"isEnabled": { "isEnabled": {
"type": "boolean" "type": "boolean"
}, },
"isReadOnly": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -6248,6 +6614,12 @@
"title": "User", "title": "User",
"type": "object", "type": "object",
"properties": { "properties": {
"accessKey": {
"type": "string"
},
"accessSecret": {
"type": "string"
},
"address": { "address": {
"type": "array", "type": "array",
"items": { "items": {
@ -6275,6 +6647,9 @@
"avatar": { "avatar": {
"type": "string" "type": "string"
}, },
"avatarType": {
"type": "string"
},
"azuread": { "azuread": {
"type": "string" "type": "string"
}, },
@ -6380,6 +6755,12 @@
"google": { "google": {
"type": "string" "type": "string"
}, },
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"hash": { "hash": {
"type": "string" "type": "string"
}, },
@ -6480,6 +6861,12 @@
"meetup": { "meetup": {
"type": "string" "type": "string"
}, },
"mfaEmailEnabled": {
"type": "boolean"
},
"mfaPhoneEnabled": {
"type": "boolean"
},
"microsoftonline": { "microsoftonline": {
"type": "string" "type": "string"
}, },
@ -6540,6 +6927,9 @@
"preHash": { "preHash": {
"type": "string" "type": "string"
}, },
"preferredMfaType": {
"type": "string"
},
"properties": { "properties": {
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
@ -6552,6 +6942,12 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"recoveryCodes": {
"type": "array",
"items": {
"type": "string"
}
},
"region": { "region": {
"type": "string" "type": "string"
}, },
@ -6605,6 +7001,9 @@
"title": { "title": {
"type": "string" "type": "string"
}, },
"totpSecret": {
"type": "string"
},
"tumblr": { "tumblr": {
"type": "string" "type": "string"
}, },
@ -6677,15 +7076,18 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"iss": { "iss": {
"type": "string" "type": "string"
}, },
"name": { "name": {
"type": "string" "type": "string"
}, },
"organization": {
"type": "string"
},
"phone": { "phone": {
"type": "string" "type": "string"
}, },
@ -6745,14 +7147,6 @@
} }
} }
}, },
"object.pricing": {
"title": "pricing",
"type": "object"
},
"object.subscription": {
"title": "subscription",
"type": "object"
},
"protocol.CredentialAssertion": { "protocol.CredentialAssertion": {
"title": "CredentialAssertion", "title": "CredentialAssertion",
"type": "object" "type": "object"

View File

@ -28,6 +28,24 @@ paths:
description: "" description: ""
schema: schema:
$ref: '#/definitions/object.OidcDiscovery' $ref: '#/definitions/object.OidcDiscovery'
/api/add-adapter:
post:
tags:
- Adapter API
description: add adapter
operationId: ApiController.AddCasbinAdapter
parameters:
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-application: /api/add-application:
post: post:
tags: tags:
@ -82,6 +100,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-group:
post:
tags:
- Group API
description: add group
operationId: ApiController.AddGroup
parameters:
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-ldap: /api/add-ldap:
post: post:
tags: tags:
@ -386,6 +422,11 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-user-keys:
post:
tags:
- User API
operationId: ApiController.AddUserkeys
/api/add-webhook: /api/add-webhook:
post: post:
tags: tags:
@ -506,12 +547,12 @@ paths:
post: post:
tags: tags:
- Enforce API - Enforce API
description: perform enforce description: Call Casbin BatchEnforce API
operationId: ApiController.BatchEnforce operationId: ApiController.BatchEnforce
parameters: parameters:
- in: body - in: body
name: body name: body
description: casbin request array description: array of casbin requests
required: true required: true
schema: schema:
$ref: '#/definitions/object.CasbinRequest' $ref: '#/definitions/object.CasbinRequest'
@ -555,6 +596,24 @@ paths:
tags: tags:
- User API - User API
operationId: ApiController.CheckUserPassword operationId: ApiController.CheckUserPassword
/api/delete-adapter:
post:
tags:
- Adapter API
description: delete adapter
operationId: ApiController.DeleteCasbinAdapter
parameters:
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-application: /api/delete-application:
post: post:
tags: tags:
@ -609,6 +668,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/delete-group:
post:
tags:
- Group API
description: delete group
operationId: ApiController.DeleteGroup
parameters:
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-ldap: /api/delete-ldap:
post: post:
tags: tags:
@ -923,12 +1000,12 @@ paths:
post: post:
tags: tags:
- Enforce API - Enforce API
description: perform enforce description: Call Casbin Enforce API
operationId: ApiController.Enforce operationId: ApiController.Enforce
parameters: parameters:
- in: body - in: body
name: body name: body
description: casbin request description: Casbin request
required: true required: true
schema: schema:
$ref: '#/definitions/object.CasbinRequest' $ref: '#/definitions/object.CasbinRequest'
@ -960,6 +1037,42 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/get-adapter:
get:
tags:
- Adapter API
description: get adapter
operationId: ApiController.GetCasbinAdapter
parameters:
- in: query
name: id
description: The id ( owner/name ) of the adapter
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Adapter'
/api/get-adapters:
get:
tags:
- Adapter API
description: get adapters
operationId: ApiController.GetCasbinAdapters
parameters:
- in: query
name: owner
description: The owner of adapters
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Adapter'
/api/get-app-login: /api/get-app-login:
get: get:
tags: tags:
@ -1183,6 +1296,42 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Cert' $ref: '#/definitions/object.Cert'
/api/get-group:
get:
tags:
- Group API
description: get group
operationId: ApiController.GetGroup
parameters:
- in: query
name: id
description: The id ( owner/name ) of the group
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Group'
/api/get-groups:
get:
tags:
- Group API
description: get groups
operationId: ApiController.GetGroups
parameters:
- in: query
name: owner
description: The owner of groups
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Group'
/api/get-ldap: /api/get-ldap:
get: get:
tags: tags:
@ -1327,7 +1476,7 @@ paths:
get: get:
tags: tags:
- Organization API - Organization API
description: get all organization names description: get all organization name and displayName
operationId: ApiController.GetOrganizationNames operationId: ApiController.GetOrganizationNames
parameters: parameters:
- in: query - in: query
@ -1521,7 +1670,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.pricing' $ref: '#/definitions/object.Pricing'
/api/get-pricings: /api/get-pricings:
get: get:
tags: tags:
@ -1793,7 +1942,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.subscription' $ref: '#/definitions/object.Subscription'
/api/get-subscriptions: /api/get-subscriptions:
get: get:
tags: tags:
@ -2172,7 +2321,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
/api/login/oauth/access_token: /api/login/oauth/access_token:
post: post:
tags: tags:
@ -2351,9 +2500,9 @@ paths:
operationId: ApiController.MfaSetupInitiate operationId: ApiController.MfaSetupInitiate
responses: responses:
"200": "200":
description: Response object description: The Response object
schema: schema:
$ref: '#/definitions/The' $ref: '#/definitions/controllers.Response'
/api/mfa/setup/verify: /api/mfa/setup/verify:
post: post:
tags: tags:
@ -2480,6 +2629,29 @@ paths:
post: post:
tags: tags:
- Login API - Login API
/api/update-adapter:
post:
tags:
- Adapter API
description: update adapter
operationId: ApiController.UpdateCasbinAdapter
parameters:
- in: query
name: id
description: The id ( owner/name ) of the adapter
required: true
type: string
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-application: /api/update-application:
post: post:
tags: tags:
@ -2549,6 +2721,29 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/update-group:
post:
tags:
- Group API
description: update group
operationId: ApiController.UpdateGroup
parameters:
- in: query
name: id
description: The id ( owner/name ) of the group
required: true
type: string
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-ldap: /api/update-ldap:
post: post:
tags: tags:
@ -2994,7 +3189,7 @@ paths:
"200": "200":
description: '"The Response object"' description: '"The Response object"'
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
/api/webauthn/signup/begin: /api/webauthn/signup/begin:
get: get:
tags: tags:
@ -3023,23 +3218,14 @@ paths:
"200": "200":
description: '"The Response object"' description: '"The Response object"'
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
definitions: definitions:
1225.0xc0002e2ae0.false:
title: "false"
type: object
1260.0xc0002e2b10.false:
title: "false"
type: object
LaravelResponse: LaravelResponse:
title: LaravelResponse title: LaravelResponse
type: object type: object
Response: Response:
title: Response title: Response
type: object type: object
The:
title: The
type: object
controllers.AuthForm: controllers.AuthForm:
title: AuthForm title: AuthForm
type: object type: object
@ -3064,9 +3250,13 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/1225.0xc0002e2ae0.false' additionalProperties:
description: support string | class | List<class> and os on
type: string
data2: data2:
$ref: '#/definitions/1260.0xc0002e2b10.false' additionalProperties:
description: support string | class | List<class> and os on
type: string
msg: msg:
type: string type: string
name: name:
@ -3090,8 +3280,8 @@ definitions:
jose.JSONWebKey: jose.JSONWebKey:
title: JSONWebKey title: JSONWebKey
type: object type: object
object.&{179844 0xc000a02f90 false}: object:
title: '&{179844 0xc000a02f90 false}' title: object
type: object type: object
object.AccountItem: object.AccountItem:
title: AccountItem title: AccountItem
@ -3220,7 +3410,7 @@ definitions:
title: CasbinRequest title: CasbinRequest
type: array type: array
items: items:
$ref: '#/definitions/object.&{179844 0xc000a02f90 false}' $ref: '#/definitions/object.CasbinRequest'
object.Cert: object.Cert:
title: Cert title: Cert
type: object type: object
@ -3295,6 +3485,44 @@ definitions:
throughput: throughput:
type: number type: number
format: double format: double
object.Group:
title: Group
type: object
properties:
children:
type: array
items:
$ref: '#/definitions/object.Group'
contactEmail:
type: string
createdTime:
type: string
displayName:
type: string
isEnabled:
type: boolean
isTopGroup:
type: boolean
key:
type: string
manager:
type: string
name:
type: string
owner:
type: string
parentId:
type: string
title:
type: string
type:
type: string
updatedTime:
type: string
users:
type: array
items:
$ref: '#/definitions/object.User'
object.Header: object.Header:
title: Header title: Header
type: object type: object
@ -3395,18 +3623,18 @@ definitions:
properties: properties:
countryCode: countryCode:
type: string type: string
id: enabled:
type: string type: boolean
isPreferred: isPreferred:
type: boolean type: boolean
mfaType:
type: string
recoveryCodes: recoveryCodes:
type: array type: array
items: items:
type: string type: string
secret: secret:
type: string type: string
type:
type: string
url: url:
type: string type: string
object.Model: object.Model:
@ -3415,6 +3643,8 @@ definitions:
properties: properties:
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
isEnabled: isEnabled:
@ -3520,6 +3750,10 @@ definitions:
type: string type: string
owner: owner:
type: string type: string
passwordOptions:
type: array
items:
type: string
passwordSalt: passwordSalt:
type: string type: string
passwordType: passwordType:
@ -3607,6 +3841,8 @@ definitions:
type: string type: string
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
domains: domains:
@ -3687,8 +3923,6 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
hasTrial:
type: boolean
isEnabled: isEnabled:
type: boolean type: boolean
name: name:
@ -3904,6 +4138,8 @@ definitions:
properties: properties:
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
domains: domains:
@ -3995,6 +4231,8 @@ definitions:
type: string type: string
isEnabled: isEnabled:
type: boolean type: boolean
isReadOnly:
type: boolean
name: name:
type: string type: string
organization: organization:
@ -4117,6 +4355,10 @@ definitions:
title: User title: User
type: object type: object
properties: properties:
accessKey:
type: string
accessSecret:
type: string
address: address:
type: array type: array
items: items:
@ -4135,6 +4377,8 @@ definitions:
type: string type: string
avatar: avatar:
type: string type: string
avatarType:
type: string
azuread: azuread:
type: string type: string
baidu: baidu:
@ -4205,6 +4449,10 @@ definitions:
type: string type: string
google: google:
type: string type: string
groups:
type: array
items:
type: string
hash: hash:
type: string type: string
heroku: heroku:
@ -4272,6 +4520,10 @@ definitions:
$ref: '#/definitions/object.ManagedAccount' $ref: '#/definitions/object.ManagedAccount'
meetup: meetup:
type: string type: string
mfaEmailEnabled:
type: boolean
mfaPhoneEnabled:
type: boolean
microsoftonline: microsoftonline:
type: string type: string
multiFactorAuths: multiFactorAuths:
@ -4312,6 +4564,8 @@ definitions:
type: string type: string
preHash: preHash:
type: string type: string
preferredMfaType:
type: string
properties: properties:
additionalProperties: additionalProperties:
type: string type: string
@ -4320,6 +4574,10 @@ definitions:
ranking: ranking:
type: integer type: integer
format: int64 format: int64
recoveryCodes:
type: array
items:
type: string
region: region:
type: string type: string
roles: roles:
@ -4356,6 +4614,8 @@ definitions:
type: string type: string
title: title:
type: string type: string
totpSecret:
type: string
tumblr: tumblr:
type: string type: string
twitch: twitch:
@ -4404,12 +4664,14 @@ definitions:
type: string type: string
email: email:
type: string type: string
groups:
type: array
items:
type: string
iss: iss:
type: string type: string
name: name:
type: string type: string
organization:
type: string
phone: phone:
type: string type: string
picture: picture:
@ -4448,12 +4710,6 @@ definitions:
type: string type: string
url: url:
type: string type: string
object.pricing:
title: pricing
type: object
object.subscription:
title: subscription
type: object
protocol.CredentialAssertion: protocol.CredentialAssertion:
title: CredentialAssertion title: CredentialAssertion
type: object type: object

View File

@ -43,6 +43,15 @@ func ContainsString(values []string, val string) bool {
return sort.SearchStrings(values, val) != len(values) return sort.SearchStrings(values, val) != len(values)
} }
func InSlice(slice []string, elem string) bool {
for _, val := range slice {
if val == elem {
return true
}
}
return false
}
func ReturnAnyNotEmpty(strs ...string) string { func ReturnAnyNotEmpty(strs ...string) string {
for _, str := range strs { for _, str := range strs {
if str != "" { if str != "" {

View File

@ -22,10 +22,18 @@ import (
"github.com/nyaruka/phonenumbers" "github.com/nyaruka/phonenumbers"
) )
var rePhone *regexp.Regexp var (
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
)
func init() { func init() {
rePhone, _ = regexp.Compile(`(\d{3})\d*(\d{4})`) rePhone, _ = regexp.Compile(`(\d{3})\d*(\d{4})`)
ReWhiteSpace, _ = regexp.Compile(`\s`)
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
} }
func IsEmailValid(email string) bool { func IsEmailValid(email string) bool {
@ -70,3 +78,7 @@ func GetCountryCode(prefix string, phone string) (string, error) {
return countryCode, nil return countryCode, nil
} }
func FilterField(field string) bool {
return ReFieldWhiteList.MatchString(field)
}

View File

@ -24,8 +24,6 @@
"i18next": "^19.8.9", "i18next": "^19.8.9",
"libphonenumber-js": "^1.10.19", "libphonenumber-js": "^1.10.19",
"moment": "^2.29.1", "moment": "^2.29.1",
"qrcode.react": "^3.1.0",
"qs": "^6.10.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-app-polyfill": "^3.0.0", "react-app-polyfill": "^3.0.0",
"react-codemirror2": "^7.2.1", "react-codemirror2": "^7.2.1",

View File

@ -76,6 +76,10 @@ class AdapterEditPage extends React.Component {
getModels(organizationName) { getModels(organizationName) {
ModelBackend.getModels(organizationName) ModelBackend.getModels(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
models: res, models: res,
}); });

View File

@ -118,20 +118,30 @@ class ApplicationEditPage extends React.Component {
getApplication() { getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
if (application === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) { if (res.status === "error") {
application.grantTypes = ["authorization_code"]; Setting.showMessage("error", res.msg);
return;
} }
if (res.grantTypes === null || res.grantTypes === undefined || res.grantTypes.length === 0) {
res.grantTypes = ["authorization_code"];
}
if (res.tags === null || res.tags === undefined) {
res.tags = [];
}
this.setState({ this.setState({
application: application, application: res,
}); });
this.getCerts(application.organization); this.getCerts(res.organization);
}); });
} }
@ -307,6 +317,18 @@ class ApplicationEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("application:Tags - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.application.tags} onChange={(value => {this.updateApplicationField("tags", value);})}>
{
this.state.application.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :

View File

@ -44,14 +44,19 @@ class CertEditPage extends React.Component {
getCert() { getCert() {
CertBackend.getCert(this.state.owner, this.state.certName) CertBackend.getCert(this.state.owner, this.state.certName)
.then((cert) => { .then((res) => {
if (cert === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
cert: cert, cert: res,
}); });
}); });
} }

View File

@ -40,17 +40,21 @@ class ChatEditPage extends React.Component {
getChat() { getChat() {
ChatBackend.getChat("admin", this.state.chatName) ChatBackend.getChat("admin", this.state.chatName)
.then((chat) => { .then((res) => {
if (chat === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
chat: chat, chat: res,
}); });
this.getUsers(chat.organization); this.getUsers(res.organization);
}); });
} }
@ -66,6 +70,11 @@ class ChatEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@ -74,8 +74,12 @@ class EntryPage extends React.Component {
}); });
ApplicationBackend.getApplication("admin", pricing.application) ApplicationBackend.getApplication("admin", pricing.application)
.then((application) => { .then((res) => {
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault; if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
const themeData = res !== null ? Setting.getThemeData(res.organizationObj, res) : Conf.ThemeDefault;
this.props.updataThemeData(themeData); this.props.updataThemeData(themeData);
}); });
}; };

View File

@ -45,17 +45,20 @@ class MessageEditPage extends React.Component {
getMessage() { getMessage() {
MessageBackend.getMessage("admin", this.state.messageName) MessageBackend.getMessage("admin", this.state.messageName)
.then((message) => { .then((res) => {
if (message === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
message: message, message: res,
}); });
this.getUsers(message.organization); this.getUsers(res.organization);
}); });
} }
@ -80,6 +83,10 @@ class MessageEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@ -47,14 +47,19 @@ class ModelEditPage extends React.Component {
getModel() { getModel() {
ModelBackend.getModel(this.state.organizationName, this.state.modelName) ModelBackend.getModel(this.state.organizationName, this.state.modelName)
.then((model) => { .then((res) => {
if (model === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
model: model, model: res,
}); });
}); });
} }

View File

@ -68,9 +68,14 @@ class OrganizationEditPage extends React.Component {
getApplications() { getApplications() {
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName) ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName)
.then((applications) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
applications: applications, applications: res,
}); });
}); });
} }

View File

@ -49,21 +49,26 @@ class PermissionEditPage extends React.Component {
getPermission() { getPermission() {
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName) PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
.then((permission) => { .then((res) => {
if (permission === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
permission: permission, permission: res,
}); });
this.getUsers(permission.owner); this.getUsers(res.owner);
this.getRoles(permission.owner); this.getRoles(res.owner);
this.getModels(permission.owner); this.getModels(res.owner);
this.getResources(permission.owner); this.getResources(res.owner);
this.getModel(permission.owner, permission.model); this.getModel(res.owner, res.model);
}); });
} }
@ -79,6 +84,10 @@ class PermissionEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });
@ -88,6 +97,10 @@ class PermissionEditPage extends React.Component {
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
roles: res, roles: res,
}); });
@ -97,6 +110,10 @@ class PermissionEditPage extends React.Component {
getModels(organizationName) { getModels(organizationName) {
ModelBackend.getModels(organizationName) ModelBackend.getModels(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
models: res, models: res,
}); });
@ -106,6 +123,10 @@ class PermissionEditPage extends React.Component {
getModel(organizationName, modelName) { getModel(organizationName, modelName) {
ModelBackend.getModel(organizationName, modelName) ModelBackend.getModel(organizationName, modelName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
model: res, model: res,
}); });

View File

@ -64,6 +64,10 @@ class PlanEditPage extends React.Component {
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
roles: res, roles: res,
}); });
@ -73,6 +77,10 @@ class PlanEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@ -49,22 +49,31 @@ class PricingEditPage extends React.Component {
getPricing() { getPricing() {
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName) PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
.then((pricing) => { .then((res) => {
if (pricing === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
pricing: pricing, pricing: res,
}); });
this.getPlans(pricing.owner); this.getPlans(res.owner);
}); });
} }
getPlans(organizationName) { getPlans(organizationName) {
PlanBackend.getPlans(organizationName) PlanBackend.getPlans(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
plans: res, plans: res,
}); });
@ -109,9 +118,13 @@ class PricingEditPage extends React.Component {
getUserApplication() { getUserApplication() {
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName) ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
.then((application) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
application: application, application: res,
}); });
}); });
} }

View File

@ -41,9 +41,14 @@ class ProductBuyPage extends React.Component {
} }
ProductBackend.getProduct(this.props.account.owner, this.state.productName) ProductBackend.getProduct(this.props.account.owner, this.state.productName)
.then((product) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
product: product, product: res,
}); });
}); });
} }

View File

@ -42,18 +42,22 @@ class RoleEditPage extends React.Component {
getRole() { getRole() {
RoleBackend.getRole(this.state.organizationName, this.state.roleName) RoleBackend.getRole(this.state.organizationName, this.state.roleName)
.then((role) => { .then((res) => {
if (role === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
role: role, role: res,
}); });
this.getUsers(role.owner); this.getUsers(res.owner);
this.getRoles(role.owner); this.getRoles(res.owner);
}); });
} }
@ -69,6 +73,10 @@ class RoleEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });
@ -78,6 +86,10 @@ class RoleEditPage extends React.Component {
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
roles: res, roles: res,
}); });

View File

@ -46,18 +46,23 @@ class SubscriptionEditPage extends React.Component {
getSubscription() { getSubscription() {
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName) SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
.then((subscription) => { .then((res) => {
if (subscription === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
subscription: subscription, subscription: res,
}); });
this.getUsers(subscription.owner); this.getUsers(res.owner);
this.getPlanes(subscription.owner); this.getPlanes(res.owner);
}); });
} }
@ -73,6 +78,10 @@ class SubscriptionEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@ -47,14 +47,19 @@ class SyncerEditPage extends React.Component {
getSyncer() { getSyncer() {
SyncerBackend.getSyncer("admin", this.state.syncerName) SyncerBackend.getSyncer("admin", this.state.syncerName)
.then((syncer) => { .then((res) => {
if (syncer === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
syncer: syncer, syncer: res,
}); });
}); });
} }

View File

@ -35,14 +35,19 @@ class TokenEditPage extends React.Component {
getToken() { getToken() {
TokenBackend.getToken("admin", this.state.tokenName) TokenBackend.getToken("admin", this.state.tokenName)
.then((token) => { .then((res) => {
if (token === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
token: token, token: res,
}); });
}); });
} }

View File

@ -121,13 +121,17 @@ class UserEditPage extends React.Component {
getUserApplication() { getUserApplication() {
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName) ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
.then((application) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
application: application, application: res,
}); });
this.setState({ this.setState({
isGroupsVisible: application.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible), isGroupsVisible: res.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
}); });
}); });
} }

View File

@ -63,8 +63,12 @@ class ForgetPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
this.onUpdateApplication(application); if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
}); });
} }
getApplicationObj() { getApplicationObj() {

View File

@ -49,7 +49,6 @@ class LoginPage extends React.Component {
username: null, username: null,
validEmailOrPhone: false, validEmailOrPhone: false,
validEmail: false, validEmail: false,
loginMethod: "password",
enableCaptchaModal: CaptchaRule.Never, enableCaptchaModal: CaptchaRule.Never,
openCaptchaModal: false, openCaptchaModal: false,
verifyCaptcha: undefined, verifyCaptcha: undefined,
@ -83,6 +82,8 @@ class LoginPage extends React.Component {
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.application !== this.props.application) { if (prevProps.application !== this.props.application) {
this.setState({loginMethod: this.getDefaultLoginMethod(this.props.application)});
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application); const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
if (captchaProviderItems) { if (captchaProviderItems) {
if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) { if (captchaProviderItems.some(providerItem => providerItem.rule === "Always")) {
@ -159,8 +160,12 @@ class LoginPage extends React.Component {
if (this.state.owner === null || this.state.type === "saml") { if (this.state.owner === null || this.state.type === "saml") {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
this.onUpdateApplication(application); if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
}); });
} else { } else {
OrganizationBackend.getDefaultApplication("admin", this.state.owner) OrganizationBackend.getDefaultApplication("admin", this.state.owner)
@ -183,6 +188,20 @@ class LoginPage extends React.Component {
return this.props.application; return this.props.application;
} }
getDefaultLoginMethod(application) {
if (application.enablePassword) {
return "password";
}
if (application.enableCodeSignin) {
return "verificationCode";
}
if (application.enableWebAuthn) {
return "webAuthn";
}
return "password";
}
onUpdateAccount(account) { onUpdateAccount(account) {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
@ -426,7 +445,8 @@ class LoginPage extends React.Component {
); );
} }
if (application.enablePassword) { const showForm = application.enablePassword || application.enableCodeSignin || application.enableWebAuthn;
if (showForm) {
let loginWidth = 320; let loginWidth = 320;
if (Setting.getLanguage() === "fr") { if (Setting.getLanguage() === "fr") {
loginWidth += 20; loginWidth += 20;
@ -511,7 +531,6 @@ class LoginPage extends React.Component {
id="input" id="input"
prefix={<UserOutlined className="site-form-item-icon" />} prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")} placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
disabled={!application.enablePassword}
onChange={e => { onChange={e => {
this.setState({ this.setState({
username: e.target.value, username: e.target.value,
@ -526,7 +545,7 @@ class LoginPage extends React.Component {
</Row> </Row>
<div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}> <div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}>
<Form.Item name="autoSignin" valuePropName="checked" noStyle> <Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}> <Checkbox style={{float: "left"}}>
{i18next.t("login:Auto sign in")} {i18next.t("login:Auto sign in")}
</Checkbox> </Checkbox>
</Form.Item> </Form.Item>
@ -540,7 +559,6 @@ class LoginPage extends React.Component {
type="primary" type="primary"
htmlType="submit" htmlType="submit"
style={{width: "100%", marginBottom: "5px"}} style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
> >
{ {
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") : this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
@ -801,14 +819,14 @@ class LoginPage extends React.Component {
renderMethodChoiceBox() { renderMethodChoiceBox() {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
const items = []; const items = [];
items.push({label: i18next.t("general:Password"), key: "password"}); application.enablePassword ? items.push({label: i18next.t("general:Password"), key: "password"}) : null;
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification code"), key: "verificationCode"}) : null; 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; application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
if (application.enableCodeSignin || application.enableWebAuthn) { if (items.length > 1) {
return ( return (
<div> <div>
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => { <Tabs items={items} size={"small"} defaultActiveKey={this.getDefaultLoginMethod(application)} onChange={(key) => {
this.setState({loginMethod: key}); this.setState({loginMethod: key});
}} centered> }} centered>
</Tabs> </Tabs>
@ -933,7 +951,7 @@ class LoginPage extends React.Component {
} }
const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem)); const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem));
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) { if (this.props.preview !== "auto" && !application.enablePassword && !application.enableCodeSignin && !application.enableWebAuthn && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup")); Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return ( return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>

View File

@ -16,8 +16,8 @@ import React, {useState} from "react";
import i18next from "i18next"; import i18next from "i18next";
import {Button, Input} from "antd"; import {Button, Input} from "antd";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage"; import {EmailMfaType, RecoveryMfaType, SmsMfaType} from "./MfaSetupPage";
import {MfaSmsVerifyForm, mfaAuth} from "./MfaVerifyForm"; import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaAuth} from "./MfaVerifyForm";
export const NextMfa = "NextMfa"; export const NextMfa = "NextMfa";
export const RequiredMfa = "RequiredMfa"; export const RequiredMfa = "RequiredMfa";
@ -60,7 +60,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
}); });
}; };
if (mfaType === SmsMfaType || mfaType === EmailMfaType) { if (mfaType !== RecoveryMfaType) {
return ( return (
<div style={{width: 300, height: 350}}> <div style={{width: 300, height: 350}}>
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}> <div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
@ -69,12 +69,18 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
<div style={{marginBottom: 24}}> <div style={{marginBottom: 24}}>
{i18next.t("mfa:Multi-factor authentication description")} {i18next.t("mfa:Multi-factor authentication description")}
</div> </div>
<MfaSmsVerifyForm {mfaType === SmsMfaType || mfaType === EmailMfaType ? (
mfaProps={mfaProps} <MfaSmsVerifyForm
method={mfaAuth} mfaProps={mfaProps}
onFinish={verify} method={mfaAuth}
application={application} onFinish={verify}
/> application={application}
/>) : (
<MfaTotpVerifyForm
mfaProps={mfaProps}
onFinish={verify}
/>
)}
<span style={{float: "right"}}> <span style={{float: "right"}}>
{i18next.t("mfa:Have problems?")} {i18next.t("mfa:Have problems?")}
<a onClick={() => { <a onClick={() => {
@ -85,7 +91,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
</span> </span>
</div> </div>
); );
} else if (mfaType === "recovery") { } else {
return ( return (
<div style={{width: 300, height: 350}}> <div style={{width: 300, height: 350}}>
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}> <div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import React, {useEffect, useState} from "react"; import React, {useState} from "react";
import {Button, Col, Form, Input, Result, Row, Steps} from "antd"; import {Button, Col, Form, Input, Result, Row, Steps} from "antd";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
@ -26,6 +26,7 @@ import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaSetup} from "./MfaVerifyForm";
export const EmailMfaType = "email"; export const EmailMfaType = "email";
export const SmsMfaType = "sms"; export const SmsMfaType = "sms";
export const TotpMfaType = "app"; export const TotpMfaType = "app";
export const RecoveryMfaType = "recovery";
function CheckPasswordForm({user, onSuccess, onFail}) { function CheckPasswordForm({user, onSuccess, onFail}) {
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -76,29 +77,11 @@ function CheckPasswordForm({user, onSuccess, onFail}) {
); );
} }
export function MfaVerifyForm({mfaType, application, user, onSuccess, onFail}) { export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail}) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [mfaProps, setMfaProps] = useState({mfaType: mfaType});
useEffect(() => {
if (mfaType === SmsMfaType) {
setMfaProps({
mfaType: mfaType,
secret: user.phone,
countryCode: user.countryCode,
});
}
if (mfaType === EmailMfaType) {
setMfaProps({
mfaType: mfaType,
secret: user.email,
});
}
}, [mfaType]);
const onFinish = ({passcode}) => { const onFinish = ({passcode}) => {
const data = {passcode, mfaType: mfaType, ...user}; const data = {passcode, mfaType: mfaProps.mfaType, ...user};
MfaBackend.MfaSetupVerify(data) MfaBackend.MfaSetupVerify(data)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
@ -115,14 +98,14 @@ export function MfaVerifyForm({mfaType, application, user, onSuccess, onFail}) {
}); });
}; };
if (mfaType === null || mfaType === undefined || mfaProps.secret === undefined) { if (mfaProps === undefined || mfaProps === null) {
return <div></div>; return <div></div>;
} }
if (mfaType === SmsMfaType || mfaType === EmailMfaType) { if (mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType) {
return <MfaSmsVerifyForm onFinish={onFinish} application={application} method={mfaSetup} mfaProps={mfaProps} />; return <MfaSmsVerifyForm mfaProps={mfaProps} onFinish={onFinish} application={application} method={mfaSetup} user={user} />;
} else if (mfaType === TotpMfaType) { } else if (mfaProps.mfaType === TotpMfaType) {
return <MfaTotpVerifyForm onFinish={onFinish} />; return <MfaTotpVerifyForm mfaProps={mfaProps} onFinish={onFinish} />;
} else { } else {
return <div></div>; return <div></div>;
} }
@ -183,7 +166,7 @@ class MfaSetupPage extends React.Component {
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.isAuthenticated === true && this.state.mfaProps === null) { if (this.state.isAuthenticated === true && (this.state.mfaProps === null || this.state.mfaType !== prevState.mfaType)) {
MfaBackend.MfaSetupInitiate({ MfaBackend.MfaSetupInitiate({
mfaType: this.state.mfaType, mfaType: this.state.mfaType,
...this.getUser(), ...this.getUser(),
@ -205,10 +188,14 @@ class MfaSetupPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
if (application !== null) { if (res !== null) {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
application: application, application: res,
}); });
} else { } else {
Setting.showMessage("error", i18next.t("mfa:Failed to get application")); Setting.showMessage("error", i18next.t("mfa:Failed to get application"));
@ -226,18 +213,20 @@ class MfaSetupPage extends React.Component {
renderStep() { renderStep() {
switch (this.state.current) { switch (this.state.current) {
case 0: case 0:
return <CheckPasswordForm return (
user={this.getUser()} <CheckPasswordForm
onSuccess={() => { user={this.getUser()}
this.setState({ onSuccess={() => {
current: this.state.current + 1, this.setState({
isAuthenticated: true, current: this.state.current + 1,
}); isAuthenticated: true,
}} });
onFail={(res) => { }}
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA")); onFail={(res) => {
}} Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
/>; }}
/>
);
case 1: case 1:
if (!this.state.isAuthenticated) { if (!this.state.isAuthenticated) {
return null; return null;
@ -246,7 +235,7 @@ class MfaSetupPage extends React.Component {
return ( return (
<div> <div>
<MfaVerifyForm <MfaVerifyForm
mfaType={this.state.mfaType} mfaProps={this.state.mfaProps}
application={this.state.application} application={this.state.application}
user={this.props.account} user={this.props.account}
onSuccess={() => { onSuccess={() => {
@ -261,11 +250,6 @@ class MfaSetupPage extends React.Component {
<Col span={24} style={{display: "flex", justifyContent: "left"}}> <Col span={24} style={{display: "flex", justifyContent: "left"}}>
{(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null : {(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null :
<Button type={"link"} onClick={() => { <Button type={"link"} onClick={() => {
if (this.state.isPromptPage) {
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${EmailMfaType}`);
} else {
this.props.history.push(`/mfa-authentication/setup?mfaType=${EmailMfaType}`);
}
this.setState({ this.setState({
mfaType: EmailMfaType, mfaType: EmailMfaType,
}); });
@ -275,17 +259,21 @@ class MfaSetupPage extends React.Component {
{ {
(this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null : (this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null :
<Button type={"link"} onClick={() => { <Button type={"link"} onClick={() => {
if (this.state.isPromptPage) {
this.props.history.push(`/prompt/${this.state.application.name}?promptType=mfa&mfaType=${SmsMfaType}`);
} else {
this.props.history.push(`/mfa-authentication/setup?mfaType=${SmsMfaType}`);
}
this.setState({ this.setState({
mfaType: SmsMfaType, mfaType: SmsMfaType,
}); });
} }
}>{i18next.t("mfa:Use SMS")}</Button> }>{i18next.t("mfa:Use SMS")}</Button>
} }
{
(this.state.mfaType === TotpMfaType) ? null :
<Button type={"link"} onClick={() => {
this.setState({
mfaType: TotpMfaType,
});
}
}>{i18next.t("mfa:Use Authenticator App")}</Button>
}
</Col> </Col>
</div> </div>
); );
@ -294,18 +282,20 @@ class MfaSetupPage extends React.Component {
return null; return null;
} }
return <EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes} return (
onSuccess={() => { <EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
Setting.showMessage("success", i18next.t("general:Enabled successfully")); onSuccess={() => {
if (this.state.isPromptPage && this.state.redirectUri) { Setting.showMessage("success", i18next.t("general:Enabled successfully"));
Setting.goToLink(this.state.redirectUri); if (this.state.isPromptPage && this.state.redirectUri) {
} else { Setting.goToLink(this.state.redirectUri);
Setting.goToLink("/account"); } else {
} Setting.goToLink("/account");
}} }
onFail={(res) => { }}
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`); onFail={(res) => {
}} />; Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
}} />
);
default: default:
return null; return null;
} }
@ -328,24 +318,20 @@ class MfaSetupPage extends React.Component {
<Col span={24} style={{justifyContent: "center"}}> <Col span={24} style={{justifyContent: "center"}}>
<Row> <Row>
<Col span={24}> <Col span={24}>
<div style={{textAlign: "center", fontSize: "28px"}}> <p style={{textAlign: "center", fontSize: "28px"}}>
{i18next.t("mfa:Protect your account with Multi-factor authentication")}</div> {i18next.t("mfa:Protect your account with Multi-factor authentication")}</p>
<div style={{textAlign: "center", fontSize: "16px", marginTop: "10px"}}>{i18next.t("mfa:Each time you sign in to your Account, you'll need your password and a authentication code")}</div> <p style={{textAlign: "center", fontSize: "16px", marginTop: "10px"}}>{i18next.t("mfa:Each time you sign in to your Account, you'll need your password and a authentication code")}</p>
</Col>
</Row>
<Row>
<Col span={24}>
<Steps current={this.state.current}
items={[
{title: i18next.t("mfa:Verify Password"), icon: <UserOutlined />},
{title: i18next.t("mfa:Verify Code"), icon: <KeyOutlined />},
{title: i18next.t("general:Enable"), icon: <CheckOutlined />},
]}
style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "80px",
}} >
</Steps>
</Col> </Col>
</Row> </Row>
<Steps current={this.state.current}
items={[
{title: i18next.t("mfa:Verify Password"), icon: <UserOutlined />},
{title: i18next.t("mfa:Verify Code"), icon: <KeyOutlined />},
{title: i18next.t("general:Enable"), icon: <CheckOutlined />},
]}
style={{width: "90%", maxWidth: "500px", margin: "auto", marginTop: "50px",
}} >
</Steps>
</Col> </Col>
<Col span={24} style={{display: "flex", justifyContent: "center"}}> <Col span={24} style={{display: "flex", justifyContent: "center"}}>
<div style={{marginTop: "10px", textAlign: "center"}}> <div style={{marginTop: "10px", textAlign: "center"}}>

View File

@ -12,23 +12,33 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Form, Input, Row} from "antd"; import {Button, Col, Form, Input, QRCode, Space} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import {CopyOutlined, UserOutlined} from "@ant-design/icons"; import {CopyOutlined, UserOutlined} from "@ant-design/icons";
import {SendCodeInput} from "../common/SendCodeInput"; import {SendCodeInput} from "../common/SendCodeInput";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import React from "react"; import React, {useEffect} from "react";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect"; import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
import {EmailMfaType} from "./MfaSetupPage"; import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
export const mfaAuth = "mfaAuth"; export const mfaAuth = "mfaAuth";
export const mfaSetup = "mfaSetup"; export const mfaSetup = "mfaSetup";
export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => { export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method, user}) => {
const [dest, setDest] = React.useState(mfaProps.secret ?? ""); const [dest, setDest] = React.useState(mfaProps.secret ?? "");
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => {
if (mfaProps.mfaType === SmsMfaType) {
setDest(user.phone);
}
if (mfaProps.mfaType === EmailMfaType) {
setDest(user.email);
}
}, [mfaProps.mfaType]);
const isEmail = () => { const isEmail = () => {
return mfaProps.mfaType === EmailMfaType; return mfaProps.mfaType === EmailMfaType;
}; };
@ -42,9 +52,9 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
countryCode: mfaProps.countryCode, countryCode: mfaProps.countryCode,
}} }}
> >
{mfaProps.secret !== "" ? {dest !== "" ?
<div style={{marginBottom: 20, textAlign: "left", gap: 8}}> <div style={{marginBottom: 20, textAlign: "left", gap: 8}}>
{isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {mfaProps.secret} {isEmail() ? i18next.t("mfa:Your email is") : i18next.t("mfa:Your phone is")} {dest}
</div> : </div> :
(<React.Fragment> (<React.Fragment>
<p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") : <p>{isEmail() ? i18next.t("mfa:Please bind your email first, the system will automatically uses the mail for multi-factor authentication") :
@ -114,44 +124,49 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => { export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const renderSecret = () => {
if (!mfaProps.secret) {
return null;
}
return (
<React.Fragment>
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
<QRCode
errorLevel="H"
value={mfaProps.url}
icon={"https://cdn.casdoor.com/static/favicon.png"}
/>
</Col>
<p style={{textAlign: "center"}}>{i18next.t("mfa:Scan the QR code with your Authenticator App")}</p>
<p style={{textAlign: "center"}}>{i18next.t("mfa:Or copy the secret to your Authenticator App")}</p>
<Col span={24}>
<Space>
<Input value={mfaProps.secret} />
<Button
type="primary"
shape="round"
icon={<CopyOutlined />}
onClick={() => {
copy(`${mfaProps.secret}`);
Setting.showMessage(
"success",
i18next.t("mfa:Multi-factor secret to clipboard successfully")
);
}}
/>
</Space>
</Col>
</React.Fragment>
);
};
return ( return (
<Form <Form
form={form} form={form}
style={{width: "300px"}} style={{width: "300px"}}
onFinish={onFinish} onFinish={onFinish}
> >
<Row type="flex" justify="center" align="middle"> {renderSecret()}
<Col>
</Col>
</Row>
<Row type="flex" justify="center" align="middle">
<Col>
{Setting.getLabel(
i18next.t("mfa:Multi-factor secret"),
i18next.t("mfa:Multi-factor secret - Tooltip")
)}
:
</Col>
<Col>
<Input value={mfaProps.secret} />
</Col>
<Col>
<Button
type="primary"
shape="round"
icon={<CopyOutlined />}
onClick={() => {
copy(`${mfaProps.secret}`);
Setting.showMessage(
"success",
i18next.t("mfa:Multi-factor secret to clipboard successfully")
);
}}
/>
</Col>
</Row>
<Form.Item <Form.Item
name="passcode" name="passcode"
rules={[{required: true, message: "Please input your passcode"}]} rules={[{required: true, message: "Please input your passcode"}]}
@ -162,7 +177,6 @@ export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
placeholder={i18next.t("mfa:Passcode")} placeholder={i18next.t("mfa:Passcode")}
/> />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button <Button
style={{marginTop: 24}} style={{marginTop: 24}}

View File

@ -49,9 +49,14 @@ class PromptPage extends React.Component {
const organizationName = this.props.account.owner; const organizationName = this.props.account.owner;
const userName = this.props.account.name; const userName = this.props.account.name;
UserBackend.getUser(organizationName, userName) UserBackend.getUser(organizationName, userName)
.then((user) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
user: user, user: res,
}); });
}); });
} }
@ -62,10 +67,15 @@ class PromptPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
this.onUpdateApplication(application); if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.setState({ this.setState({
application: application, application: res,
}); });
}); });
} }

View File

@ -43,10 +43,14 @@ class ResultPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
this.onUpdateApplication(application); if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.setState({ this.setState({
application: application, application: res,
}); });
}); });
} }

View File

@ -108,8 +108,13 @@ class SignupPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", applicationName) ApplicationBackend.getApplication("admin", applicationName)
.then((application) => { .then((res) => {
this.onUpdateApplication(application); if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
}); });
} }

View File

@ -127,6 +127,11 @@ export const CropperDivModal = (props) => {
setLoading(true); setLoading(true);
ResourceBackend.getResources(user.owner, user.name, "", "", "", "", "", "") ResourceBackend.getResources(user.owner, user.name, "", "", "", "", "", "")
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
setLoading(false);
return;
}
setLoading(false); setLoading(false);
setOptions(getOptions(res)); setOptions(getOptions(res));
}); });

View File

@ -96,6 +96,7 @@
"Signup items": "Registrierungs Items", "Signup items": "Registrierungs Items",
"Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren", "Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Registrierungsseite wurde in die Zwischenablage kopiert. Bitte fügen Sie sie in einen Inkognito-Tab oder einen anderen Browser ein", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Registrierungsseite wurde in die Zwischenablage kopiert. Bitte fügen Sie sie in einen Inkognito-Tab oder einen anderen Browser ein",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren", "The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
"Token expire": "Token läuft ab", "Token expire": "Token läuft ab",
"Token expire - Tooltip": "Ablaufzeit des Access-Tokens", "Token expire - Tooltip": "Ablaufzeit des Access-Tokens",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Preisseite URL kopieren", "Copy pricing page URL": "Preisseite URL kopieren",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Kostenlos", "Free": "Kostenlos",
"Failed to get plans": "Es konnten keine Pläne abgerufen werden",
"Getting started": "Loslegen", "Getting started": "Loslegen",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Testphase Dauer", "Trial duration": "Testphase Dauer",

View File

@ -96,6 +96,7 @@
"Signup items": "Signup items", "Signup items": "Signup items",
"Signup items - Tooltip": "Items for users to fill in when registering new accounts", "Signup items - Tooltip": "Items for users to fill in when registering new accounts",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "The application does not allow to sign up new account", "The application does not allow to sign up new account": "The application does not allow to sign up new account",
"Token expire": "Token expire", "Token expire": "Token expire",
"Token expire - Tooltip": "Access token expiration time", "Token expire - Tooltip": "Access token expiration time",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "If you are unable to access your device, enter your recovery code to verify your identity", "Multi-factor recover description": "If you are unable to access your device, enter your recovery code to verify your identity",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Copy pricing page URL", "Copy pricing page URL": "Copy pricing page URL",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Free", "Free": "Free",
"Failed to get plans": "Failed to get plans",
"Getting started": "Getting started", "Getting started": "Getting started",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Trial duration", "Trial duration": "Trial duration",

View File

@ -96,6 +96,7 @@
"Signup items": "Artículos de registro", "Signup items": "Artículos de registro",
"Signup items - Tooltip": "Elementos para que los usuarios los completen al registrar nuevas cuentas", "Signup items - Tooltip": "Elementos para que los usuarios los completen al registrar nuevas cuentas",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "La URL de la página de registro se ha copiado correctamente en el portapapeles. Por favor, péguela en una ventana de incógnito o en otro navegador", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "La URL de la página de registro se ha copiado correctamente en el portapapeles. Por favor, péguela en una ventana de incógnito o en otro navegador",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "La aplicación no permite registrarse una cuenta nueva", "The application does not allow to sign up new account": "La aplicación no permite registrarse una cuenta nueva",
"Token expire": "Token expirado", "Token expire": "Token expirado",
"Token expire - Tooltip": "Tiempo de expiración del token de acceso", "Token expire - Tooltip": "Tiempo de expiración del token de acceso",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Copiar URL de la página de precios", "Copy pricing page URL": "Copiar URL de la página de precios",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Gratis", "Free": "Gratis",
"Failed to get plans": "No se pudieron obtener los planes",
"Getting started": "Empezar", "Getting started": "Empezar",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Duración del período de prueba", "Trial duration": "Duración del período de prueba",

View File

@ -96,6 +96,7 @@
"Signup items": "Les éléments d'inscription", "Signup items": "Les éléments d'inscription",
"Signup items - Tooltip": "Eléments à remplir par les utilisateurs lors de l'inscription de nouveaux comptes", "Signup items - Tooltip": "Eléments à remplir par les utilisateurs lors de l'inscription de nouveaux comptes",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL de la page d'inscription copiée avec succès dans le presse-papiers, veuillez la coller dans la fenêtre de navigation privée ou dans un autre navigateur", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL de la page d'inscription copiée avec succès dans le presse-papiers, veuillez la coller dans la fenêtre de navigation privée ou dans un autre navigateur",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "L'application ne permet pas de créer un nouveau compte", "The application does not allow to sign up new account": "L'application ne permet pas de créer un nouveau compte",
"Token expire": "Le jeton expire", "Token expire": "Le jeton expire",
"Token expire - Tooltip": "Temps d'expiration de jeton d'accès", "Token expire - Tooltip": "Temps d'expiration de jeton d'accès",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Copier l'URL de la page tarifs", "Copy pricing page URL": "Copier l'URL de la page tarifs",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Gratuit", "Free": "Gratuit",
"Failed to get plans": "Échec de l'obtention des plans",
"Getting started": "Commencer", "Getting started": "Commencer",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Durée de l'essai", "Trial duration": "Durée de l'essai",

View File

@ -96,6 +96,7 @@
"Signup items": "Item pendaftaran", "Signup items": "Item pendaftaran",
"Signup items - Tooltip": "Item-item yang harus diisi pengguna saat mendaftar untuk akun baru", "Signup items - Tooltip": "Item-item yang harus diisi pengguna saat mendaftar untuk akun baru",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Tautan halaman pendaftaran URL berhasil disalin ke papan klip, silakan tempelkan ke dalam jendela incognito atau browser lain", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Tautan halaman pendaftaran URL berhasil disalin ke papan klip, silakan tempelkan ke dalam jendela incognito atau browser lain",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "Aplikasi tidak memperbolehkan untuk mendaftar akun baru", "The application does not allow to sign up new account": "Aplikasi tidak memperbolehkan untuk mendaftar akun baru",
"Token expire": "Token kadaluarsa", "Token expire": "Token kadaluarsa",
"Token expire - Tooltip": "Waktu kadaluwarsa token akses", "Token expire - Tooltip": "Waktu kadaluwarsa token akses",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Salin URL halaman harga", "Copy pricing page URL": "Salin URL halaman harga",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Gratis", "Free": "Gratis",
"Failed to get plans": "Gagal mendapatkan rencana",
"Getting started": "Mulai", "Getting started": "Mulai",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Durasi percobaan", "Trial duration": "Durasi percobaan",

View File

@ -96,6 +96,7 @@
"Signup items": "サインアップアイテム", "Signup items": "サインアップアイテム",
"Signup items - Tooltip": "新しいアカウントを登録する際にユーザーが入力するアイテム", "Signup items - Tooltip": "新しいアカウントを登録する際にユーザーが入力するアイテム",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "サインアップページのURLがクリップボードに正常にコピーされました。シークレットウィンドウまたは別のブラウザに貼り付けてください", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "サインアップページのURLがクリップボードに正常にコピーされました。シークレットウィンドウまたは別のブラウザに貼り付けてください",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "アプリケーションでは新しいアカウントの登録ができません", "The application does not allow to sign up new account": "アプリケーションでは新しいアカウントの登録ができません",
"Token expire": "トークンの有効期限が切れました", "Token expire": "トークンの有効期限が切れました",
"Token expire - Tooltip": "アクセストークンの有効期限", "Token expire - Tooltip": "アクセストークンの有効期限",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "価格ページのURLをコピー", "Copy pricing page URL": "価格ページのURLをコピー",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "無料", "Free": "無料",
"Failed to get plans": "計画の取得に失敗しました",
"Getting started": "はじめる", "Getting started": "はじめる",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "トライアル期間の長さ", "Trial duration": "トライアル期間の長さ",

View File

@ -96,6 +96,7 @@
"Signup items": "가입 항목", "Signup items": "가입 항목",
"Signup items - Tooltip": "새로운 계정 등록시 사용자가 작성해야하는 항목들", "Signup items - Tooltip": "새로운 계정 등록시 사용자가 작성해야하는 항목들",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "가입 페이지 URL이 클립보드에 성공적으로 복사되었습니다. 시크릿 창이나 다른 브라우저에 붙여넣어 주십시오", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "가입 페이지 URL이 클립보드에 성공적으로 복사되었습니다. 시크릿 창이나 다른 브라우저에 붙여넣어 주십시오",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "이 어플리케이션은 새 계정 등록을 허용하지 않습니다", "The application does not allow to sign up new account": "이 어플리케이션은 새 계정 등록을 허용하지 않습니다",
"Token expire": "토큰 만료", "Token expire": "토큰 만료",
"Token expire - Tooltip": "액세스 토큰 만료 시간", "Token expire - Tooltip": "액세스 토큰 만료 시간",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "가격 페이지 URL 복사", "Copy pricing page URL": "가격 페이지 URL 복사",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "무료", "Free": "무료",
"Failed to get plans": "계획을 가져오지 못했습니다.",
"Getting started": "시작하기", "Getting started": "시작하기",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "체험 기간", "Trial duration": "체험 기간",

View File

@ -96,6 +96,7 @@
"Signup items": "Itens de registro", "Signup items": "Itens de registro",
"Signup items - Tooltip": "Itens para os usuários preencherem ao registrar novas contas", "Signup items - Tooltip": "Itens para os usuários preencherem ao registrar novas contas",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL da página de registro copiada para a área de transferência com sucesso. Cole-a na janela anônima ou em outro navegador", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL da página de registro copiada para a área de transferência com sucesso. Cole-a na janela anônima ou em outro navegador",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "A aplicação não permite o registro de novas contas", "The application does not allow to sign up new account": "A aplicação não permite o registro de novas contas",
"Token expire": "Expiração do Token", "Token expire": "Expiração do Token",
"Token expire - Tooltip": "Tempo de expiração do token de acesso", "Token expire - Tooltip": "Tempo de expiração do token de acesso",
@ -434,17 +435,18 @@
"Multi-factor methods": "Métodos de vários fatores", "Multi-factor methods": "Métodos de vários fatores",
"Multi-factor recover": "Recuperação de vários fatores", "Multi-factor recover": "Recuperação de vários fatores",
"Multi-factor recover description": "Se você não conseguir acessar seu dispositivo, insira seu código de recuperação para verificar sua identidade", "Multi-factor recover description": "Se você não conseguir acessar seu dispositivo, insira seu código de recuperação para verificar sua identidade",
"Multi-factor secret": "Segredo de vários fatores",
"Multi-factor secret - Tooltip": "Segredo de vários fatores - Dica de ferramenta",
"Multi-factor secret to clipboard successfully": "Segredo de vários fatores copiado para a área de transferência com sucesso", "Multi-factor secret to clipboard successfully": "Segredo de vários fatores copiado para a área de transferência com sucesso",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Código de acesso", "Passcode": "Código de acesso",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Guarde este código de recuperação. Quando o seu dispositivo não puder fornecer um código de autenticação, você poderá redefinir a autenticação mfa usando este código de recuperação", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Guarde este código de recuperação. Quando o seu dispositivo não puder fornecer um código de autenticação, você poderá redefinir a autenticação mfa usando este código de recuperação",
"Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores", "Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores",
"Recovery code": "Código de recuperação", "Recovery code": "Código de recuperação",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Definir preferido", "Set preferred": "Definir preferido",
"Setup": "Configuração", "Setup": "Configuração",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Usar código de verificação SMS", "Use SMS verification code": "Usar código de verificação SMS",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Sao chép URL trang bảng giá", "Copy pricing page URL": "Sao chép URL trang bảng giá",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Miễn phí", "Free": "Miễn phí",
"Failed to get plans": "Falha ao obter planos",
"Getting started": "Bắt đầu", "Getting started": "Bắt đầu",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Thời gian thử nghiệm", "Trial duration": "Thời gian thử nghiệm",

View File

@ -96,6 +96,7 @@
"Signup items": "Элементы регистрации", "Signup items": "Элементы регистрации",
"Signup items - Tooltip": "Элементы, которые пользователи должны заполнить при регистрации новых аккаунтов", "Signup items - Tooltip": "Элементы, которые пользователи должны заполнить при регистрации новых аккаунтов",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Успешно скопирован URL страницы регистрации в буфер обмена, пожалуйста, вставьте его в режиме инкогнито или в другом браузере", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Успешно скопирован URL страницы регистрации в буфер обмена, пожалуйста, вставьте его в режиме инкогнито или в другом браузере",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "Приложение не позволяет зарегистрироваться новому аккаунту", "The application does not allow to sign up new account": "Приложение не позволяет зарегистрироваться новому аккаунту",
"Token expire": "Срок действия токена истекает", "Token expire": "Срок действия токена истекает",
"Token expire - Tooltip": "Время истечения токена доступа", "Token expire - Tooltip": "Время истечения токена доступа",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Скопировать URL прайс-листа", "Copy pricing page URL": "Скопировать URL прайс-листа",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Бесплатно", "Free": "Бесплатно",
"Failed to get plans": "Не удалось получить планы",
"Getting started": "Выьрать план", "Getting started": "Выьрать план",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Продолжительность пробного периода", "Trial duration": "Продолжительность пробного периода",

View File

@ -96,6 +96,7 @@
"Signup items": "Các mục đăng ký", "Signup items": "Các mục đăng ký",
"Signup items - Tooltip": "Các thông tin cần được người dùng điền khi đăng ký tài khoản mới", "Signup items - Tooltip": "Các thông tin cần được người dùng điền khi đăng ký tài khoản mới",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Đã sao chép thành công đường dẫn trang đăng ký vào clipboard, vui lòng dán nó vào cửa sổ ẩn danh hoặc trình duyệt khác", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Đã sao chép thành công đường dẫn trang đăng ký vào clipboard, vui lòng dán nó vào cửa sổ ẩn danh hoặc trình duyệt khác",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "Ứng dụng không cho phép đăng ký tài khoản mới", "The application does not allow to sign up new account": "Ứng dụng không cho phép đăng ký tài khoản mới",
"Token expire": "Mã thông báo hết hạn", "Token expire": "Mã thông báo hết hạn",
"Token expire - Tooltip": "Thời gian hết hạn của mã truy cập", "Token expire - Tooltip": "Thời gian hết hạn của mã truy cập",
@ -434,17 +435,18 @@
"Multi-factor methods": "Multi-factor methods", "Multi-factor methods": "Multi-factor methods",
"Multi-factor recover": "Multi-factor recover", "Multi-factor recover": "Multi-factor recover",
"Multi-factor recover description": "Multi-factor recover description", "Multi-factor recover description": "Multi-factor recover description",
"Multi-factor secret": "Multi-factor secret",
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
"Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully", "Multi-factor secret to clipboard successfully": "Multi-factor secret to clipboard successfully",
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
"Passcode": "Passcode", "Passcode": "Passcode",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "Please bind your email first, the system will automatically uses the mail for multi-factor authentication",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "Please bind your phone first, the system automatically uses the phone for multi-factor authentication",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code",
"Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication", "Protect your account with Multi-factor authentication": "Protect your account with Multi-factor authentication",
"Recovery code": "Recovery code", "Recovery code": "Recovery code",
"Scan the QR code with your Authenticator App": "Scan the QR code with your Authenticator App",
"Set preferred": "Set preferred", "Set preferred": "Set preferred",
"Setup": "Setup", "Setup": "Setup",
"Use Authenticator App": "Use Authenticator App",
"Use Email": "Use Email", "Use Email": "Use Email",
"Use SMS": "Use SMS", "Use SMS": "Use SMS",
"Use SMS verification code": "Use SMS verification code", "Use SMS verification code": "Use SMS verification code",
@ -568,6 +570,7 @@
"Copy pricing page URL": "Sao chép URL trang bảng giá", "Copy pricing page URL": "Sao chép URL trang bảng giá",
"Edit Pricing": "Edit Pricing", "Edit Pricing": "Edit Pricing",
"Free": "Miễn phí", "Free": "Miễn phí",
"Failed to get plans": "Không thể lấy được các kế hoạch",
"Getting started": "Bắt đầu", "Getting started": "Bắt đầu",
"New Pricing": "New Pricing", "New Pricing": "New Pricing",
"Trial duration": "Thời gian thử nghiệm", "Trial duration": "Thời gian thử nghiệm",

View File

@ -96,6 +96,7 @@
"Signup items": "注册项", "Signup items": "注册项",
"Signup items - Tooltip": "注册用户注册时需要填写的项目", "Signup items - Tooltip": "注册用户注册时需要填写的项目",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "注册页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问", "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "注册页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Tags - Tooltip": "用户的标签在应用的标签集合中时,用户才可以登录该应用",
"The application does not allow to sign up new account": "该应用不允许注册新账户", "The application does not allow to sign up new account": "该应用不允许注册新账户",
"Token expire": "Access Token过期", "Token expire": "Access Token过期",
"Token expire - Tooltip": "Access Token过期时间", "Token expire - Tooltip": "Access Token过期时间",
@ -434,17 +435,18 @@
"Multi-factor methods": "多因素认证方式", "Multi-factor methods": "多因素认证方式",
"Multi-factor recover": "重置多因素认证", "Multi-factor recover": "重置多因素认证",
"Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份", "Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份",
"Multi-factor secret": "多因素密钥",
"Multi-factor secret - Tooltip": "多因素密钥 - Tooltip",
"Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板", "Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板",
"Or copy the secret to your Authenticator App": "或者将这个密钥复制到你的身份验证应用中",
"Passcode": "认证码", "Passcode": "认证码",
"Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式", "Please bind your email first, the system will automatically uses the mail for multi-factor authentication": "请先绑定邮箱,之后会自动使用该邮箱作为多因素认证的方式",
"Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式", "Please bind your phone first, the system automatically uses the phone for multi-factor authentication": "请先绑定手机号,之后会自动使用该手机号作为多因素认证的方式",
"Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证", "Please save this recovery code. Once your device cannot provide an authentication code, you can reset mfa authentication by this recovery code": "请保存此恢复代码。一旦您的设备无法提供身份验证码,您可以通过此恢复码重置多因素认证",
"Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户", "Protect your account with Multi-factor authentication": "通过多因素认证保护您的帐户",
"Recovery code": "恢复码", "Recovery code": "恢复码",
"Scan the QR code with your Authenticator App": "用你的身份验证应用扫描二维码",
"Set preferred": "设为首选", "Set preferred": "设为首选",
"Setup": "设置", "Setup": "设置",
"Use Authenticator App": "使用身份验证应用",
"Use Email": "使用电子邮件", "Use Email": "使用电子邮件",
"Use SMS": "使用短信", "Use SMS": "使用短信",
"Use SMS verification code": "使用手机或电子邮件发送验证码认证", "Use SMS verification code": "使用手机或电子邮件发送验证码认证",
@ -568,6 +570,7 @@
"Copy pricing page URL": "复制定价页面链接", "Copy pricing page URL": "复制定价页面链接",
"Edit Pricing": "编辑定价", "Edit Pricing": "编辑定价",
"Free": "免费", "Free": "免费",
"Failed to get plans": "未能获取计划",
"Getting started": "开始使用", "Getting started": "开始使用",
"New Pricing": "添加定价", "New Pricing": "添加定价",
"Trial duration": "试用期时长", "Trial duration": "试用期时长",

View File

@ -64,6 +64,11 @@ class PricingPage extends React.Component {
Promise.all(plans) Promise.all(plans)
.then(results => { .then(results => {
const hasError = results.some(result => result.status === "error");
if (hasError) {
Setting.showMessage("error", `${i18next.t("Failed to get plans")}`);
return;
}
this.setState({ this.setState({
plans: results, plans: results,
loading: false, loading: false,
@ -81,6 +86,11 @@ class PricingPage extends React.Component {
PricingBackend.getPricing(this.state.owner, pricingName) PricingBackend.getPricing(this.state.owner, pricingName)
.then((result) => { .then((result) => {
if (result.status === "error") {
Setting.showMessage("error", result.msg);
return;
}
this.setState({ this.setState({
loading: false, loading: false,
pricing: result, pricing: result,

File diff suppressed because it is too large Load Diff