mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-07 05:17:48 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8080b10b3b | ||
![]() |
cd7589775c | ||
![]() |
0a8c2a35fe | ||
![]() |
d1e734e4ce | ||
![]() |
68f032b54d | ||
![]() |
1780620ef4 | ||
![]() |
5c968ed1ce | ||
![]() |
4016fc0f65 | ||
![]() |
463b3ad976 | ||
![]() |
b817a55f9f | ||
![]() |
2c2ddfbb92 |
@@ -50,7 +50,8 @@ func (c *ApiController) GetApplications() {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
||||
@@ -59,13 +60,15 @@ func (c *ApiController) GetApplications() {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetApplicationCount(owner, field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
applications := object.GetMaskedApplications(app, userId)
|
||||
@@ -85,7 +88,8 @@ func (c *ApiController) GetApplication() {
|
||||
id := c.Input().Get("id")
|
||||
app, err := object.GetApplication(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplication(app, userId)
|
||||
@@ -104,7 +108,8 @@ func (c *ApiController) GetUserApplication() {
|
||||
id := c.Input().Get("id")
|
||||
user, err := object.GetUser(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
@@ -114,7 +119,8 @@ func (c *ApiController) GetUserApplication() {
|
||||
|
||||
app, err := object.GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplication(app, userId)
|
||||
@@ -147,7 +153,8 @@ func (c *ApiController) GetOrganizationApplications() {
|
||||
if limit == "" || page == "" {
|
||||
applications, err := object.GetOrganizationApplications(owner, organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
||||
|
@@ -756,7 +756,8 @@ func (c *ApiController) HandleSamlLogin() {
|
||||
func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var data struct {
|
||||
@@ -766,7 +767,8 @@ func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
}
|
||||
err = xml.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
|
@@ -79,7 +79,8 @@ func (c *ApiController) getCurrentUser() *object.User {
|
||||
} else {
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return user
|
||||
@@ -112,7 +113,8 @@ func (c *ApiController) GetSessionApplication() *object.Application {
|
||||
}
|
||||
application, err := object.GetApplicationByClientId(clientId.(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return application
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetCerts() {
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedCerts
|
||||
@@ -50,13 +51,15 @@ func (c *ApiController) GetCerts() {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetCertCount(owner, field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
@@ -80,7 +83,8 @@ func (c *ApiController) GetGlobleCerts() {
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedCerts
|
||||
@@ -89,13 +93,15 @@ func (c *ApiController) GetGlobleCerts() {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalCertsCount(field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
@@ -113,7 +119,8 @@ func (c *ApiController) GetCert() {
|
||||
id := c.Input().Get("id")
|
||||
cert, err := object.GetCert(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedCert(cert)
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetChats() {
|
||||
if limit == "" || page == "" {
|
||||
maskedChats, err := object.GetMaskedChats(object.GetChats(owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedChats
|
||||
@@ -77,7 +78,8 @@ func (c *ApiController) GetChat() {
|
||||
|
||||
maskedChat, err := object.GetMaskedChat(object.GetChat(id))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedChat
|
||||
|
@@ -44,14 +44,26 @@ func (c *ApiController) Enforce() {
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
enforceResult, err := object.Enforce(permissionId, &request)
|
||||
permission, err := object.GetPermission(permissionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
res = append(res, enforceResult)
|
||||
|
||||
if permission == nil {
|
||||
res = append(res, false)
|
||||
} else {
|
||||
enforceResult, err := object.Enforce(permission, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
return
|
||||
}
|
||||
@@ -76,8 +88,16 @@ func (c *ApiController) Enforce() {
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
for _, permission := range permissions {
|
||||
enforceResult, err := object.Enforce(permission.GetId(), &request)
|
||||
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -85,6 +105,7 @@ func (c *ApiController) Enforce() {
|
||||
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
}
|
||||
|
||||
@@ -109,14 +130,32 @@ func (c *ApiController) BatchEnforce() {
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
enforceResult, err := object.BatchEnforce(permissionId, &requests)
|
||||
permission, err := object.GetPermission(permissionId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
res = append(res, enforceResult)
|
||||
|
||||
if permission == nil {
|
||||
l := len(requests)
|
||||
resRequest := make([]bool, l)
|
||||
for i := 0; i < l; i++ {
|
||||
resRequest[i] = false
|
||||
}
|
||||
|
||||
res = append(res, resRequest)
|
||||
} else {
|
||||
enforceResult, err := object.BatchEnforce(permission, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
return
|
||||
}
|
||||
@@ -135,8 +174,16 @@ func (c *ApiController) BatchEnforce() {
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
for _, permission := range permissions {
|
||||
enforceResult, err := object.BatchEnforce(permission.GetId(), &requests)
|
||||
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -144,6 +191,7 @@ func (c *ApiController) BatchEnforce() {
|
||||
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
}
|
||||
|
||||
|
@@ -53,7 +53,8 @@ func (c *ApiController) GetMessages() {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessages(messages)
|
||||
@@ -89,7 +90,8 @@ func (c *ApiController) GetMessage() {
|
||||
id := c.Input().Get("id")
|
||||
message, err := object.GetMessage(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
_, err := c.Ctx.ResponseWriter.Write([]byte(event))
|
||||
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")
|
||||
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
answer := stringBuilder.String()
|
||||
@@ -204,7 +208,8 @@ func (c *ApiController) GetMessageAnswer() {
|
||||
message.Text = answer
|
||||
_, err = object.UpdateMessage(message.GetId(), message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,6 @@ package controllers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@@ -58,10 +57,7 @@ func (c *ApiController) MfaSetupInitiate() {
|
||||
return
|
||||
}
|
||||
|
||||
issuer := beego.AppConfig.String("appname")
|
||||
accountName := user.GetId()
|
||||
|
||||
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
|
||||
mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetModels() {
|
||||
if limit == "" || page == "" {
|
||||
models, err := object.GetModels(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = models
|
||||
@@ -77,7 +78,8 @@ func (c *ApiController) GetModel() {
|
||||
|
||||
model, err := object.GetModel(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = model
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetOrganizations() {
|
||||
if limit == "" || page == "" {
|
||||
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedOrganizations
|
||||
|
@@ -42,7 +42,8 @@ func (c *ApiController) GetPayments() {
|
||||
if limit == "" || page == "" {
|
||||
payments, err := object.GetPayments(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = payments
|
||||
@@ -51,13 +52,15 @@ func (c *ApiController) GetPayments() {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPaymentCount(owner, organization, field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payments, paginator.Nums())
|
||||
@@ -99,7 +102,8 @@ func (c *ApiController) GetPayment() {
|
||||
|
||||
payment, err := object.GetPayment(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = payment
|
||||
@@ -190,7 +194,8 @@ func (c *ApiController) NotifyPayment() {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetPermissions() {
|
||||
if limit == "" || page == "" {
|
||||
permissions, err := object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = permissions
|
||||
@@ -50,13 +51,15 @@ func (c *ApiController) GetPermissions() {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPermissionCount(owner, field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(permissions, paginator.Nums())
|
||||
@@ -116,7 +119,8 @@ func (c *ApiController) GetPermission() {
|
||||
|
||||
permission, err := object.GetPermission(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = permission
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetPlans() {
|
||||
if limit == "" || page == "" {
|
||||
plans, err := object.GetPlans(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = plans
|
||||
@@ -79,13 +80,15 @@ func (c *ApiController) GetPlan() {
|
||||
|
||||
plan, err := object.GetPlan(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if includeOption {
|
||||
options, err := object.GetPermissionsByRole(plan.Role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetPricings() {
|
||||
if limit == "" || page == "" {
|
||||
pricings, err := object.GetPricings(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = pricings
|
||||
@@ -77,7 +78,8 @@ func (c *ApiController) GetPricing() {
|
||||
|
||||
pricing, err := object.GetPricing(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = pricing
|
||||
|
@@ -42,7 +42,8 @@ func (c *ApiController) GetProducts() {
|
||||
if limit == "" || page == "" {
|
||||
products, err := object.GetProducts(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = products
|
||||
@@ -78,12 +79,14 @@ func (c *ApiController) GetProduct() {
|
||||
|
||||
product, err := object.GetProduct(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.ExtendProductWithProviders(product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = product
|
||||
|
@@ -46,7 +46,8 @@ func (c *ApiController) GetProviders() {
|
||||
if limit == "" || page == "" {
|
||||
providers, err := object.GetProviders(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
|
||||
@@ -92,7 +93,8 @@ func (c *ApiController) GetGlobalProviders() {
|
||||
if limit == "" || page == "" {
|
||||
globalProviders, err := object.GetGlobalProviders()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))
|
||||
|
@@ -46,7 +46,8 @@ func (c *ApiController) GetRecords() {
|
||||
if limit == "" || page == "" {
|
||||
records, err := object.GetRecords()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = records
|
||||
@@ -84,12 +85,14 @@ func (c *ApiController) GetRecordsByFilter() {
|
||||
record := &object.Record{}
|
||||
err := util.JsonToStruct(body, record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
records, err := object.GetRecordsByField(record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = records
|
||||
|
@@ -53,7 +53,8 @@ func (c *ApiController) GetResources() {
|
||||
if limit == "" || page == "" {
|
||||
resources, err := object.GetResources(owner, user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = resources
|
||||
@@ -86,7 +87,8 @@ func (c *ApiController) GetResource() {
|
||||
|
||||
resource, err := object.GetResource(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = resource
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetRoles() {
|
||||
if limit == "" || page == "" {
|
||||
roles, err := object.GetRoles(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = roles
|
||||
@@ -77,7 +78,8 @@ func (c *ApiController) GetRole() {
|
||||
|
||||
role, err := object.GetRole(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = role
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetSessions() {
|
||||
if limit == "" || page == "" {
|
||||
sessions, err := object.GetSessions(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = sessions
|
||||
@@ -76,7 +77,8 @@ func (c *ApiController) GetSingleSession() {
|
||||
|
||||
session, err := object.GetSingleSession(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = session
|
||||
@@ -155,7 +157,8 @@ func (c *ApiController) IsSessionDuplicated() {
|
||||
|
||||
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetSubscriptions() {
|
||||
if limit == "" || page == "" {
|
||||
subscriptions, err := object.GetSubscriptions(owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = subscriptions
|
||||
@@ -77,7 +78,8 @@ func (c *ApiController) GetSubscription() {
|
||||
|
||||
subscription, err := object.GetSubscription(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = subscription
|
||||
|
@@ -42,7 +42,8 @@ func (c *ApiController) GetSyncers() {
|
||||
if limit == "" || page == "" {
|
||||
organizationSyncers, err := object.GetOrganizationSyncers(owner, organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = organizationSyncers
|
||||
@@ -78,7 +79,8 @@ func (c *ApiController) GetSyncer() {
|
||||
|
||||
syncer, err := object.GetSyncer(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = syncer
|
||||
|
@@ -43,7 +43,8 @@ func (c *ApiController) GetTokens() {
|
||||
if limit == "" || page == "" {
|
||||
token, err := object.GetTokens(owner, organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = token
|
||||
@@ -78,7 +79,8 @@ func (c *ApiController) GetToken() {
|
||||
id := c.Input().Get("id")
|
||||
token, err := object.GetToken(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = token
|
||||
@@ -193,7 +195,8 @@ func (c *ApiController) GetOAuthToken() {
|
||||
host := c.Ctx.Request.Host
|
||||
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = oAuthtoken
|
||||
@@ -236,7 +239,8 @@ func (c *ApiController) RefreshToken() {
|
||||
|
||||
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = refreshToken2
|
||||
@@ -276,7 +280,8 @@ func (c *ApiController) IntrospectToken() {
|
||||
}
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
@@ -289,7 +294,8 @@ func (c *ApiController) IntrospectToken() {
|
||||
}
|
||||
token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if token == nil {
|
||||
|
@@ -41,7 +41,8 @@ func (c *ApiController) GetGlobalUsers() {
|
||||
if limit == "" || page == "" {
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedUsers
|
||||
@@ -101,7 +102,8 @@ func (c *ApiController) GetUsers() {
|
||||
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedUsers
|
||||
@@ -153,7 +155,8 @@ func (c *ApiController) GetUser() {
|
||||
if userId != "" && owner != "" {
|
||||
userFromUserId, err = object.GetUserByUserId(owner, userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||
@@ -165,7 +168,8 @@ func (c *ApiController) GetUser() {
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.IsProfilePublic {
|
||||
@@ -190,18 +194,21 @@ func (c *ApiController) GetUser() {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
|
||||
err = object.ExtendUserWithRolesAndPermissions(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
maskedUser, err := object.GetMaskedUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedUser
|
||||
@@ -416,6 +423,7 @@ func (c *ApiController) SetPassword() {
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" && code == "" {
|
||||
c.ResponseError(c.T("general:Please login first"), "Please login first")
|
||||
return
|
||||
} else if code == "" {
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
|
||||
@@ -425,7 +433,7 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
} else {
|
||||
if code != c.GetSession("verifiedCode") {
|
||||
c.ResponseError("")
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
c.SetSession("verifiedCode", "")
|
||||
@@ -497,7 +505,8 @@ func (c *ApiController) GetSortedUsers() {
|
||||
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedUsers
|
||||
|
@@ -97,7 +97,8 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
|
@@ -42,7 +42,8 @@ func (c *ApiController) GetWebhooks() {
|
||||
if limit == "" || page == "" {
|
||||
webhooks, err := object.GetWebhooks(owner, organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = webhooks
|
||||
@@ -79,7 +80,8 @@ func (c *ApiController) GetWebhook() {
|
||||
|
||||
webhook, err := object.GetWebhook(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = webhook
|
||||
|
4
go.mod
4
go.mod
@@ -42,7 +42,7 @@ require (
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
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_model v0.2.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
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/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
|
7
go.sum
7
go.sum
@@ -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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
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/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
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/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
|
||||
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/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.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
@@ -595,7 +597,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.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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/beego/beego/context"
|
||||
)
|
||||
|
||||
const MfaRecoveryCodesSession = "mfa_recovery_codes"
|
||||
|
||||
type MfaSessionData struct {
|
||||
UserId string
|
||||
}
|
||||
@@ -37,10 +39,10 @@ type MfaProps struct {
|
||||
}
|
||||
|
||||
type MfaInterface interface {
|
||||
SetupVerify(ctx *context.Context, passCode string) error
|
||||
Verify(passCode string) error
|
||||
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
|
||||
Initiate(ctx *context.Context, userId string) (*MfaProps, error)
|
||||
SetupVerify(ctx *context.Context, passcode string) error
|
||||
Enable(ctx *context.Context, user *User) error
|
||||
Verify(passcode string) error
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -58,11 +60,11 @@ const (
|
||||
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
|
||||
switch mfaType {
|
||||
case SmsType:
|
||||
return NewSmsTwoFactor(config)
|
||||
return NewSmsMfaUtil(config)
|
||||
case EmailType:
|
||||
return NewEmailTwoFactor(config)
|
||||
return NewEmailMfaUtil(config)
|
||||
case TotpType:
|
||||
return nil
|
||||
return NewTotpMfaUtil(config)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -97,23 +99,9 @@ func MfaRecover(user *User, recoveryCode string) error {
|
||||
func GetAllMfaProps(user *User, masked bool) []*MfaProps {
|
||||
mfaProps := []*MfaProps{}
|
||||
|
||||
if user.MfaPhoneEnabled {
|
||||
mfaProps = append(mfaProps, user.GetMfaProps(SmsType, masked))
|
||||
} else {
|
||||
mfaProps = append(mfaProps, &MfaProps{
|
||||
Enabled: false,
|
||||
MfaType: SmsType,
|
||||
})
|
||||
for _, mfaType := range []string{SmsType, EmailType, TotpType} {
|
||||
mfaProps = append(mfaProps, user.GetMfaProps(mfaType, masked))
|
||||
}
|
||||
if user.MfaEmailEnabled {
|
||||
mfaProps = append(mfaProps, user.GetMfaProps(EmailType, masked))
|
||||
} else {
|
||||
mfaProps = append(mfaProps, &MfaProps{
|
||||
Enabled: false,
|
||||
MfaType: EmailType,
|
||||
})
|
||||
}
|
||||
|
||||
return mfaProps
|
||||
}
|
||||
|
||||
@@ -121,6 +109,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
||||
mfaProps := &MfaProps{}
|
||||
|
||||
if mfaType == SmsType {
|
||||
if !user.MfaPhoneEnabled {
|
||||
return &MfaProps{
|
||||
Enabled: false,
|
||||
MfaType: mfaType,
|
||||
}
|
||||
}
|
||||
|
||||
mfaProps = &MfaProps{
|
||||
Enabled: user.MfaPhoneEnabled,
|
||||
MfaType: mfaType,
|
||||
@@ -132,6 +127,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
||||
mfaProps.Secret = user.Phone
|
||||
}
|
||||
} else if mfaType == EmailType {
|
||||
if !user.MfaEmailEnabled {
|
||||
return &MfaProps{
|
||||
Enabled: false,
|
||||
MfaType: mfaType,
|
||||
}
|
||||
}
|
||||
|
||||
mfaProps = &MfaProps{
|
||||
Enabled: user.MfaEmailEnabled,
|
||||
MfaType: mfaType,
|
||||
@@ -142,9 +144,22 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
|
||||
mfaProps.Secret = user.Email
|
||||
}
|
||||
} else if mfaType == TotpType {
|
||||
if user.TotpSecret == "" {
|
||||
return &MfaProps{
|
||||
Enabled: false,
|
||||
MfaType: mfaType,
|
||||
}
|
||||
}
|
||||
|
||||
mfaProps = &MfaProps{
|
||||
Enabled: true,
|
||||
MfaType: mfaType,
|
||||
}
|
||||
if masked {
|
||||
mfaProps.Secret = ""
|
||||
} else {
|
||||
mfaProps.Secret = user.TotpSecret
|
||||
}
|
||||
}
|
||||
|
||||
if user.PreferredMfaType == mfaType {
|
||||
@@ -158,8 +173,9 @@ func DisabledMultiFactorAuth(user *User) error {
|
||||
user.RecoveryCodes = []string{}
|
||||
user.MfaPhoneEnabled = 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 {
|
||||
return err
|
||||
}
|
||||
|
@@ -18,26 +18,24 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaSmsCountryCodeSession = "mfa_country_code"
|
||||
MfaSmsDestSession = "mfa_dest"
|
||||
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
|
||||
MfaSmsCountryCodeSession = "mfa_country_code"
|
||||
MfaSmsDestSession = "mfa_dest"
|
||||
)
|
||||
|
||||
type SmsMfa struct {
|
||||
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()
|
||||
|
||||
err := ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode})
|
||||
err := ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
|
||||
if err != nil {
|
||||
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 {
|
||||
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string)
|
||||
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
|
||||
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"}
|
||||
@@ -111,7 +109,7 @@ func (mfa *SmsMfa) Verify(passCode string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
|
||||
func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
|
||||
if config == nil {
|
||||
config = &MfaProps{
|
||||
MfaType: SmsType,
|
||||
@@ -122,7 +120,7 @@ func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
|
||||
}
|
||||
}
|
||||
|
||||
func NewEmailTwoFactor(config *MfaProps) *SmsMfa {
|
||||
func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
|
||||
if config == nil {
|
||||
config = &MfaProps{
|
||||
MfaType: EmailType,
|
||||
|
133
object/mfa_totp.go
Normal file
133
object/mfa_totp.go
Normal 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,
|
||||
}
|
||||
}
|
@@ -370,3 +370,24 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
// GroupPermissionsByModelAdapter group permissions by model and adapter.
|
||||
// Every model and adapter will be a key, and the value is a list of permission ids.
|
||||
// With each list of permission ids have the same key, we just need to init the
|
||||
// enforcer and do the enforce/batch-enforce once (with list of permission ids
|
||||
// as the policyFilter when the enforcer load policy).
|
||||
func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string {
|
||||
m := make(map[string][]string)
|
||||
|
||||
for _, permission := range permissions {
|
||||
key := permission.Model + permission.Adapter
|
||||
permissionIds, ok := m[key]
|
||||
if !ok {
|
||||
m[key] = []string{permission.GetId()}
|
||||
} else {
|
||||
m[key] = append(permissionIds, permission.GetId())
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ import (
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
)
|
||||
|
||||
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforcer {
|
||||
tableName := "permission_rule"
|
||||
if len(permission.Adapter) != 0 {
|
||||
adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter)
|
||||
@@ -77,8 +77,13 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
|
||||
enforcer.SetAdapter(adapter)
|
||||
|
||||
policyFilterV5 := []string{permission.GetId()}
|
||||
if len(permissionIDs) != 0 {
|
||||
policyFilterV5 = permissionIDs
|
||||
}
|
||||
|
||||
policyFilter := xormadapter.Filter{
|
||||
V5: []string{permission.GetId()},
|
||||
V5: policyFilterV5,
|
||||
}
|
||||
|
||||
if !HasRoleDefinition(m) {
|
||||
@@ -241,28 +246,13 @@ func removePolicies(permission *Permission) {
|
||||
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
func Enforce(permissionId string, request *CasbinRequest) (bool, error) {
|
||||
permission, err := GetPermission(permissionId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
enforcer := getEnforcer(permission)
|
||||
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
|
||||
enforcer := getEnforcer(permission, permissionIds...)
|
||||
return enforcer.Enforce(*request...)
|
||||
}
|
||||
|
||||
func BatchEnforce(permissionId string, requests *[]CasbinRequest) ([]bool, error) {
|
||||
permission, err := GetPermission(permissionId)
|
||||
if err != nil {
|
||||
res := []bool{}
|
||||
for i := 0; i < len(*requests); i++ {
|
||||
res = append(res, false)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
enforcer := getEnforcer(permission)
|
||||
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
|
||||
enforcer := getEnforcer(permission, permissionIds...)
|
||||
return enforcer.BatchEnforce(*requests)
|
||||
}
|
||||
|
||||
|
@@ -50,6 +50,7 @@ type Syncer struct {
|
||||
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
||||
ErrorText string `xorm:"mediumtext" json:"errorText"`
|
||||
SyncInterval int `json:"syncInterval"`
|
||||
IsReadOnly bool `json:"isReadOnly"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
|
||||
Adapter *Adapter `xorm:"-" json:"-"`
|
||||
|
@@ -63,9 +63,11 @@ func (syncer *Syncer) syncUsers() {
|
||||
}
|
||||
} else {
|
||||
if user.PreHash == oHash {
|
||||
updatedOUser := syncer.createOriginalUserFromUser(user)
|
||||
syncer.updateUser(updatedOUser)
|
||||
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
|
||||
if !syncer.IsReadOnly {
|
||||
updatedOUser := syncer.createOriginalUserFromUser(user)
|
||||
syncer.updateUser(updatedOUser)
|
||||
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
|
||||
}
|
||||
|
||||
// update preHash
|
||||
user.PreHash = user.Hash
|
||||
@@ -91,15 +93,17 @@ func (syncer *Syncer) syncUsers() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
id := user.Id
|
||||
if _, ok := oUserMap[id]; !ok {
|
||||
newOUser := syncer.createOriginalUserFromUser(user)
|
||||
_, err = syncer.addUser(newOUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if !syncer.IsReadOnly {
|
||||
for _, user := range users {
|
||||
id := user.Id
|
||||
if _, ok := oUserMap[id]; !ok {
|
||||
newOUser := syncer.createOriginalUserFromUser(user)
|
||||
_, err = syncer.addUser(newOUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
}
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -170,6 +170,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
|
||||
originalUser := &OriginalUser{
|
||||
Address: []string{},
|
||||
Properties: map[string]string{},
|
||||
Groups: []string{},
|
||||
}
|
||||
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
|
@@ -161,6 +161,7 @@ type User struct {
|
||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
|
||||
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"`
|
||||
TotpSecret string `xorm:"varchar(100)" json:"totpSecret,omitempty"`
|
||||
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
||||
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
||||
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
|
||||
@@ -432,16 +433,19 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
|
||||
if user.AccessSecret != "" {
|
||||
user.AccessSecret = "***"
|
||||
}
|
||||
if user.RecoveryCodes != nil {
|
||||
user.RecoveryCodes = nil
|
||||
}
|
||||
|
||||
if user.ManagedAccounts != nil {
|
||||
for _, manageAccount := range user.ManagedAccounts {
|
||||
manageAccount.Password = "***"
|
||||
}
|
||||
}
|
||||
|
||||
if user.TotpSecret != "" {
|
||||
user.TotpSecret = ""
|
||||
}
|
||||
if user.RecoveryCodes != nil {
|
||||
user.RecoveryCodes = nil
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
@@ -24,8 +24,6 @@
|
||||
"i18next": "^19.8.9",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
|
@@ -76,6 +76,10 @@ class AdapterEditPage extends React.Component {
|
||||
getModels(organizationName) {
|
||||
ModelBackend.getModels(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
models: res,
|
||||
});
|
||||
|
@@ -343,9 +343,11 @@ class App extends Component {
|
||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||
"/account"
|
||||
));
|
||||
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||
"/chat"
|
||||
));
|
||||
if (Conf.EnableChatPages) {
|
||||
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||
"/chat"
|
||||
));
|
||||
}
|
||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||
"/logout"));
|
||||
|
||||
@@ -411,6 +413,14 @@ class App extends Component {
|
||||
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
|
||||
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
if (Conf.ShowGithubCorner) {
|
||||
res.push(Setting.getItem(<a href={"https://casdoor.com"}>
|
||||
<span style={{fontWeight: "bold", backgroundColor: "rgba(87,52,211,0.4)", marginTop: "12px", paddingLeft: "5px", paddingRight: "5px", display: "flex", alignItems: "center", height: "40px", borderRadius: "5px"}}>
|
||||
🚀 SaaS Hosting 🔥
|
||||
</span>
|
||||
</a>, "#"));
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
|
||||
"/organizations"));
|
||||
|
||||
@@ -449,13 +459,15 @@ class App extends Component {
|
||||
"/providers"
|
||||
));
|
||||
|
||||
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
|
||||
"/chats"
|
||||
));
|
||||
if (Conf.EnableChatPages) {
|
||||
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
|
||||
"/chats"
|
||||
));
|
||||
|
||||
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
|
||||
"/messages"
|
||||
));
|
||||
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
|
||||
"/messages"
|
||||
));
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
|
||||
"/resources"
|
||||
@@ -476,7 +488,6 @@ class App extends Component {
|
||||
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
|
||||
"/subscriptions"
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
@@ -501,7 +512,6 @@ class App extends Component {
|
||||
));
|
||||
|
||||
if (Conf.EnableExtraPages) {
|
||||
|
||||
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
|
||||
"/products"
|
||||
));
|
||||
@@ -510,8 +520,8 @@ class App extends Component {
|
||||
"/payments"
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (Setting.isAdminUser(this.state.account)) {
|
||||
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
|
||||
"/sysinfo"
|
||||
|
@@ -118,20 +118,25 @@ class ApplicationEditPage extends React.Component {
|
||||
|
||||
getApplication() {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
if (application === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
|
||||
application.grantTypes = ["authorization_code"];
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.grantTypes === null || res.grantTypes === undefined || res.grantTypes.length === 0) {
|
||||
res.grantTypes = ["authorization_code"];
|
||||
}
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
|
||||
this.getCerts(application.organization);
|
||||
this.getCerts(res.organization);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -44,14 +44,19 @@ class CertEditPage extends React.Component {
|
||||
|
||||
getCert() {
|
||||
CertBackend.getCert(this.state.owner, this.state.certName)
|
||||
.then((cert) => {
|
||||
if (cert === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cert: cert,
|
||||
cert: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -40,17 +40,21 @@ class ChatEditPage extends React.Component {
|
||||
|
||||
getChat() {
|
||||
ChatBackend.getChat("admin", this.state.chatName)
|
||||
.then((chat) => {
|
||||
if (chat === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
|
@@ -21,6 +21,8 @@ export const DefaultLanguage = "en";
|
||||
|
||||
export const EnableExtraPages = true;
|
||||
|
||||
export const EnableChatPages = true;
|
||||
|
||||
export const InitThemeAlgorithm = true;
|
||||
export const ThemeDefault = {
|
||||
themeType: "default",
|
||||
|
@@ -74,8 +74,12 @@ class EntryPage extends React.Component {
|
||||
});
|
||||
|
||||
ApplicationBackend.getApplication("admin", pricing.application)
|
||||
.then((application) => {
|
||||
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
|
||||
.then((res) => {
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
@@ -45,17 +45,20 @@ class MessageEditPage extends React.Component {
|
||||
|
||||
getMessage() {
|
||||
MessageBackend.getMessage("admin", this.state.messageName)
|
||||
.then((message) => {
|
||||
if (message === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
|
@@ -47,14 +47,19 @@ class ModelEditPage extends React.Component {
|
||||
|
||||
getModel() {
|
||||
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
|
||||
.then((model) => {
|
||||
if (model === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
model: model,
|
||||
model: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -68,9 +68,14 @@ class OrganizationEditPage extends React.Component {
|
||||
|
||||
getApplications() {
|
||||
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName)
|
||||
.then((applications) => {
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
applications: applications,
|
||||
applications: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -49,21 +49,26 @@ class PermissionEditPage extends React.Component {
|
||||
|
||||
getPermission() {
|
||||
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
|
||||
.then((permission) => {
|
||||
if (permission === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
permission: permission,
|
||||
permission: res,
|
||||
});
|
||||
|
||||
this.getUsers(permission.owner);
|
||||
this.getRoles(permission.owner);
|
||||
this.getModels(permission.owner);
|
||||
this.getResources(permission.owner);
|
||||
this.getModel(permission.owner, permission.model);
|
||||
this.getUsers(res.owner);
|
||||
this.getRoles(res.owner);
|
||||
this.getModels(res.owner);
|
||||
this.getResources(res.owner);
|
||||
this.getModel(res.owner, res.model);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,6 +84,10 @@ class PermissionEditPage extends React.Component {
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
@@ -88,6 +97,10 @@ class PermissionEditPage extends React.Component {
|
||||
getRoles(organizationName) {
|
||||
RoleBackend.getRoles(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
roles: res,
|
||||
});
|
||||
@@ -97,6 +110,10 @@ class PermissionEditPage extends React.Component {
|
||||
getModels(organizationName) {
|
||||
ModelBackend.getModels(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
models: res,
|
||||
});
|
||||
@@ -106,6 +123,10 @@ class PermissionEditPage extends React.Component {
|
||||
getModel(organizationName, modelName) {
|
||||
ModelBackend.getModel(organizationName, modelName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
model: res,
|
||||
});
|
||||
|
@@ -64,6 +64,10 @@ class PlanEditPage extends React.Component {
|
||||
getRoles(organizationName) {
|
||||
RoleBackend.getRoles(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
roles: res,
|
||||
});
|
||||
@@ -73,6 +77,10 @@ class PlanEditPage extends React.Component {
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
|
@@ -49,22 +49,31 @@ class PricingEditPage extends React.Component {
|
||||
|
||||
getPricing() {
|
||||
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
|
||||
.then((pricing) => {
|
||||
if (pricing === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
pricing: pricing,
|
||||
pricing: res,
|
||||
});
|
||||
this.getPlans(pricing.owner);
|
||||
this.getPlans(res.owner);
|
||||
});
|
||||
}
|
||||
|
||||
getPlans(organizationName) {
|
||||
PlanBackend.getPlans(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
plans: res,
|
||||
});
|
||||
@@ -109,9 +118,13 @@ class PricingEditPage extends React.Component {
|
||||
|
||||
getUserApplication() {
|
||||
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
|
||||
.then((application) => {
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -41,9 +41,14 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
|
||||
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({
|
||||
product: product,
|
||||
product: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -42,18 +42,22 @@ class RoleEditPage extends React.Component {
|
||||
|
||||
getRole() {
|
||||
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
|
||||
.then((role) => {
|
||||
if (role === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
role: role,
|
||||
role: res,
|
||||
});
|
||||
|
||||
this.getUsers(role.owner);
|
||||
this.getRoles(role.owner);
|
||||
this.getUsers(res.owner);
|
||||
this.getRoles(res.owner);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,6 +73,10 @@ class RoleEditPage extends React.Component {
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
@@ -78,6 +86,10 @@ class RoleEditPage extends React.Component {
|
||||
getRoles(organizationName) {
|
||||
RoleBackend.getRoles(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
roles: res,
|
||||
});
|
||||
|
@@ -46,18 +46,23 @@ class SubscriptionEditPage extends React.Component {
|
||||
|
||||
getSubscription() {
|
||||
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
|
||||
.then((subscription) => {
|
||||
if (subscription === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
subscription: subscription,
|
||||
subscription: res,
|
||||
});
|
||||
|
||||
this.getUsers(subscription.owner);
|
||||
this.getPlanes(subscription.owner);
|
||||
this.getUsers(res.owner);
|
||||
this.getPlanes(res.owner);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +78,10 @@ class SubscriptionEditPage extends React.Component {
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
|
@@ -47,14 +47,19 @@ class SyncerEditPage extends React.Component {
|
||||
|
||||
getSyncer() {
|
||||
SyncerBackend.getSyncer("admin", this.state.syncerName)
|
||||
.then((syncer) => {
|
||||
if (syncer === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
syncer: syncer,
|
||||
syncer: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -376,6 +381,16 @@ class SyncerEditPage extends React.Component {
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Is read-only"), i18next.t("syncer:Is read-only - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.syncer.isReadOnly} onChange={checked => {
|
||||
this.updateSyncerField("isReadOnly", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||
|
@@ -42,6 +42,7 @@ class SyncerListPage extends BaseListPage {
|
||||
affiliationTable: "",
|
||||
avatarBaseUrl: "",
|
||||
syncInterval: 10,
|
||||
isReadOnly: false,
|
||||
isEnabled: false,
|
||||
};
|
||||
}
|
||||
|
@@ -35,14 +35,19 @@ class TokenEditPage extends React.Component {
|
||||
|
||||
getToken() {
|
||||
TokenBackend.getToken("admin", this.state.tokenName)
|
||||
.then((token) => {
|
||||
if (token === null) {
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
token: token,
|
||||
token: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -121,13 +121,17 @@ class UserEditPage extends React.Component {
|
||||
|
||||
getUserApplication() {
|
||||
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
|
||||
.then((application) => {
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isGroupsVisible: application.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
|
||||
isGroupsVisible: res.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -270,7 +270,7 @@ class UserListPage extends BaseListPage {
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
<img src={text} alt={text} width={50} />
|
||||
<img referrerPolicy="no-referrer" src={text} alt={text} width={50} />
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
@@ -63,8 +63,12 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.onUpdateApplication(res);
|
||||
});
|
||||
}
|
||||
getApplicationObj() {
|
||||
|
@@ -49,7 +49,6 @@ class LoginPage extends React.Component {
|
||||
username: null,
|
||||
validEmailOrPhone: false,
|
||||
validEmail: false,
|
||||
loginMethod: "password",
|
||||
enableCaptchaModal: CaptchaRule.Never,
|
||||
openCaptchaModal: false,
|
||||
verifyCaptcha: undefined,
|
||||
@@ -83,6 +82,8 @@ class LoginPage extends React.Component {
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (prevProps.application !== this.props.application) {
|
||||
this.setState({loginMethod: this.getDefaultLoginMethod(this.props.application)});
|
||||
|
||||
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
|
||||
if (captchaProviderItems) {
|
||||
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") {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.onUpdateApplication(res);
|
||||
});
|
||||
} else {
|
||||
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
|
||||
@@ -183,6 +188,20 @@ class LoginPage extends React.Component {
|
||||
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) {
|
||||
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;
|
||||
if (Setting.getLanguage() === "fr") {
|
||||
loginWidth += 20;
|
||||
@@ -511,7 +531,6 @@ class LoginPage extends React.Component {
|
||||
id="input"
|
||||
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")}
|
||||
disabled={!application.enablePassword}
|
||||
onChange={e => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
@@ -526,7 +545,7 @@ class LoginPage extends React.Component {
|
||||
</Row>
|
||||
<div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}>
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||
<Checkbox style={{float: "left"}}>
|
||||
{i18next.t("login:Auto sign in")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
@@ -540,7 +559,6 @@ class LoginPage extends React.Component {
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{width: "100%", marginBottom: "5px"}}
|
||||
disabled={!application.enablePassword}
|
||||
>
|
||||
{
|
||||
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
||||
@@ -801,14 +819,14 @@ class LoginPage extends React.Component {
|
||||
renderMethodChoiceBox() {
|
||||
const application = this.getApplicationObj();
|
||||
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.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
|
||||
|
||||
if (application.enableCodeSignin || application.enableWebAuthn) {
|
||||
if (items.length > 1) {
|
||||
return (
|
||||
<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});
|
||||
}} centered>
|
||||
</Tabs>
|
||||
@@ -933,7 +951,7 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
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"));
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
|
||||
|
@@ -16,8 +16,8 @@ import React, {useState} from "react";
|
||||
import i18next from "i18next";
|
||||
import {Button, Input} from "antd";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
|
||||
import {MfaSmsVerifyForm, mfaAuth} from "./MfaVerifyForm";
|
||||
import {EmailMfaType, RecoveryMfaType, SmsMfaType} from "./MfaSetupPage";
|
||||
import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaAuth} from "./MfaVerifyForm";
|
||||
|
||||
export const NextMfa = "NextMfa";
|
||||
export const RequiredMfa = "RequiredMfa";
|
||||
@@ -60,7 +60,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
||||
});
|
||||
};
|
||||
|
||||
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
|
||||
if (mfaType !== RecoveryMfaType) {
|
||||
return (
|
||||
<div style={{width: 300, height: 350}}>
|
||||
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
||||
@@ -69,12 +69,18 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
||||
<div style={{marginBottom: 24}}>
|
||||
{i18next.t("mfa:Multi-factor authentication description")}
|
||||
</div>
|
||||
<MfaSmsVerifyForm
|
||||
mfaProps={mfaProps}
|
||||
method={mfaAuth}
|
||||
onFinish={verify}
|
||||
application={application}
|
||||
/>
|
||||
{mfaType === SmsMfaType || mfaType === EmailMfaType ? (
|
||||
<MfaSmsVerifyForm
|
||||
mfaProps={mfaProps}
|
||||
method={mfaAuth}
|
||||
onFinish={verify}
|
||||
application={application}
|
||||
/>) : (
|
||||
<MfaTotpVerifyForm
|
||||
mfaProps={mfaProps}
|
||||
onFinish={verify}
|
||||
/>
|
||||
)}
|
||||
<span style={{float: "right"}}>
|
||||
{i18next.t("mfa:Have problems?")}
|
||||
<a onClick={() => {
|
||||
@@ -85,7 +91,7 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else if (mfaType === "recovery") {
|
||||
} else {
|
||||
return (
|
||||
<div style={{width: 300, height: 350}}>
|
||||
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
||||
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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 * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Setting from "../Setting";
|
||||
@@ -26,6 +26,7 @@ import {MfaSmsVerifyForm, MfaTotpVerifyForm, mfaSetup} from "./MfaVerifyForm";
|
||||
export const EmailMfaType = "email";
|
||||
export const SmsMfaType = "sms";
|
||||
export const TotpMfaType = "app";
|
||||
export const RecoveryMfaType = "recovery";
|
||||
|
||||
function CheckPasswordForm({user, onSuccess, onFail}) {
|
||||
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 [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 data = {passcode, mfaType: mfaType, ...user};
|
||||
const data = {passcode, mfaType: mfaProps.mfaType, ...user};
|
||||
MfaBackend.MfaSetupVerify(data)
|
||||
.then((res) => {
|
||||
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>;
|
||||
}
|
||||
|
||||
if (mfaType === SmsMfaType || mfaType === EmailMfaType) {
|
||||
return <MfaSmsVerifyForm onFinish={onFinish} application={application} method={mfaSetup} mfaProps={mfaProps} />;
|
||||
} else if (mfaType === TotpMfaType) {
|
||||
return <MfaTotpVerifyForm onFinish={onFinish} />;
|
||||
if (mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType) {
|
||||
return <MfaSmsVerifyForm mfaProps={mfaProps} onFinish={onFinish} application={application} method={mfaSetup} user={user} />;
|
||||
} else if (mfaProps.mfaType === TotpMfaType) {
|
||||
return <MfaTotpVerifyForm mfaProps={mfaProps} onFinish={onFinish} />;
|
||||
} else {
|
||||
return <div></div>;
|
||||
}
|
||||
@@ -183,7 +166,7 @@ class MfaSetupPage extends React.Component {
|
||||
}
|
||||
|
||||
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({
|
||||
mfaType: this.state.mfaType,
|
||||
...this.getUser(),
|
||||
@@ -205,10 +188,14 @@ class MfaSetupPage extends React.Component {
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
if (application !== null) {
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("mfa:Failed to get application"));
|
||||
@@ -226,18 +213,20 @@ class MfaSetupPage extends React.Component {
|
||||
renderStep() {
|
||||
switch (this.state.current) {
|
||||
case 0:
|
||||
return <CheckPasswordForm
|
||||
user={this.getUser()}
|
||||
onSuccess={() => {
|
||||
this.setState({
|
||||
current: this.state.current + 1,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
}}
|
||||
onFail={(res) => {
|
||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||
}}
|
||||
/>;
|
||||
return (
|
||||
<CheckPasswordForm
|
||||
user={this.getUser()}
|
||||
onSuccess={() => {
|
||||
this.setState({
|
||||
current: this.state.current + 1,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
}}
|
||||
onFail={(res) => {
|
||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
if (!this.state.isAuthenticated) {
|
||||
return null;
|
||||
@@ -246,7 +235,7 @@ class MfaSetupPage extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<MfaVerifyForm
|
||||
mfaType={this.state.mfaType}
|
||||
mfaProps={this.state.mfaProps}
|
||||
application={this.state.application}
|
||||
user={this.props.account}
|
||||
onSuccess={() => {
|
||||
@@ -261,11 +250,6 @@ class MfaSetupPage extends React.Component {
|
||||
<Col span={24} style={{display: "flex", justifyContent: "left"}}>
|
||||
{(this.state.mfaType === EmailMfaType || this.props.account.mfaEmailEnabled) ? null :
|
||||
<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({
|
||||
mfaType: EmailMfaType,
|
||||
});
|
||||
@@ -275,17 +259,21 @@ class MfaSetupPage extends React.Component {
|
||||
{
|
||||
(this.state.mfaType === SmsMfaType || this.props.account.mfaPhoneEnabled) ? null :
|
||||
<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({
|
||||
mfaType: SmsMfaType,
|
||||
});
|
||||
}
|
||||
}>{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>
|
||||
</div>
|
||||
);
|
||||
@@ -294,18 +282,20 @@ class MfaSetupPage extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
|
||||
onSuccess={() => {
|
||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||
Setting.goToLink(this.state.redirectUri);
|
||||
} else {
|
||||
Setting.goToLink("/account");
|
||||
}
|
||||
}}
|
||||
onFail={(res) => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||
}} />;
|
||||
return (
|
||||
<EnableMfaForm user={this.getUser()} mfaType={this.state.mfaType} recoveryCodes={this.state.mfaProps.recoveryCodes}
|
||||
onSuccess={() => {
|
||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||
Setting.goToLink(this.state.redirectUri);
|
||||
} else {
|
||||
Setting.goToLink("/account");
|
||||
}
|
||||
}}
|
||||
onFail={(res) => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||
}} />
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -328,24 +318,20 @@ class MfaSetupPage extends React.Component {
|
||||
<Col span={24} style={{justifyContent: "center"}}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<div style={{textAlign: "center", fontSize: "28px"}}>
|
||||
{i18next.t("mfa:Protect your account with Multi-factor authentication")}</div>
|
||||
<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>
|
||||
</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>
|
||||
<p style={{textAlign: "center", fontSize: "28px"}}>
|
||||
{i18next.t("mfa:Protect your account with Multi-factor authentication")}</p>
|
||||
<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>
|
||||
<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 span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||
|
@@ -12,23 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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 {CopyOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import * as Setting from "../Setting";
|
||||
import React from "react";
|
||||
import React, {useEffect} from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
||||
import {EmailMfaType} from "./MfaSetupPage";
|
||||
import {EmailMfaType, SmsMfaType} from "./MfaSetupPage";
|
||||
|
||||
export const mfaAuth = "mfaAuth";
|
||||
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 [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (mfaProps.mfaType === SmsMfaType) {
|
||||
setDest(user.phone);
|
||||
}
|
||||
|
||||
if (mfaProps.mfaType === EmailMfaType) {
|
||||
setDest(user.email);
|
||||
}
|
||||
}, [mfaProps.mfaType]);
|
||||
|
||||
const isEmail = () => {
|
||||
return mfaProps.mfaType === EmailMfaType;
|
||||
};
|
||||
@@ -42,9 +52,9 @@ export const MfaSmsVerifyForm = ({mfaProps, application, onFinish, method}) => {
|
||||
countryCode: mfaProps.countryCode,
|
||||
}}
|
||||
>
|
||||
{mfaProps.secret !== "" ?
|
||||
{dest !== "" ?
|
||||
<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> :
|
||||
(<React.Fragment>
|
||||
<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}) => {
|
||||
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 (
|
||||
<Form
|
||||
form={form}
|
||||
style={{width: "300px"}}
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Row type="flex" justify="center" align="middle">
|
||||
<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>
|
||||
|
||||
{renderSecret()}
|
||||
<Form.Item
|
||||
name="passcode"
|
||||
rules={[{required: true, message: "Please input your passcode"}]}
|
||||
@@ -162,7 +177,6 @@ export const MfaTotpVerifyForm = ({mfaProps, onFinish}) => {
|
||||
placeholder={i18next.t("mfa:Passcode")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
style={{marginTop: 24}}
|
||||
|
@@ -49,9 +49,14 @@ class PromptPage extends React.Component {
|
||||
const organizationName = this.props.account.owner;
|
||||
const userName = this.props.account.name;
|
||||
UserBackend.getUser(organizationName, userName)
|
||||
.then((user) => {
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
user: user,
|
||||
user: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -62,10 +67,15 @@ class PromptPage extends React.Component {
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onUpdateApplication(res);
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -43,10 +43,14 @@ class ResultPage extends React.Component {
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
this.onUpdateApplication(res);
|
||||
this.setState({
|
||||
application: application,
|
||||
application: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -108,8 +108,13 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onUpdateApplication(res);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -127,6 +127,11 @@ export const CropperDivModal = (props) => {
|
||||
setLoading(true);
|
||||
ResourceBackend.getResources(user.owner, user.name, "", "", "", "", "", "")
|
||||
.then((res) => {
|
||||
if (res.status === "error") {
|
||||
Setting.showMessage("error", res.msg);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setOptions(getOptions(res));
|
||||
});
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Preisseite URL kopieren",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Kostenlos",
|
||||
"Failed to get plans": "Es konnten keine Pläne abgerufen werden",
|
||||
"Getting started": "Loslegen",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Testphase Dauer",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Fehlermeldung",
|
||||
"Error text - Tooltip": "Fehler Text",
|
||||
"Is hashed": "ist gehasht",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Neuer Syncer",
|
||||
"Sync interval": "Synchronisierungsintervall",
|
||||
"Sync interval - Tooltip": "Einheit in Sekunden",
|
||||
|
@@ -434,17 +434,18 @@
|
||||
"Multi-factor methods": "Multi-factor methods",
|
||||
"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 secret": "Multi-factor secret",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Copy pricing page URL",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Free",
|
||||
"Failed to get plans": "Failed to get plans",
|
||||
"Getting started": "Getting started",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Trial duration",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Error text",
|
||||
"Error text - Tooltip": "Error text",
|
||||
"Is hashed": "Is hashed",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Unit in seconds",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Copiar URL de la página de precios",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratis",
|
||||
"Failed to get plans": "No se pudieron obtener los planes",
|
||||
"Getting started": "Empezar",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Duración del período de prueba",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Texto de error",
|
||||
"Error text - Tooltip": "Texto de error",
|
||||
"Is hashed": "Está encriptado",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Nuevo Syncer",
|
||||
"Sync interval": "Intervalo de sincronización",
|
||||
"Sync interval - Tooltip": "Unidad en segundos",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Copier l'URL de la page tarifs",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratuit",
|
||||
"Failed to get plans": "Échec de l'obtention des plans",
|
||||
"Getting started": "Commencer",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Durée de l'essai",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Texte d'erreur",
|
||||
"Error text - Tooltip": "Texte d'erreur",
|
||||
"Is hashed": "Est-haché",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Nouveau synchroniseur",
|
||||
"Sync interval": "Intervalle de synchronisation",
|
||||
"Sync interval - Tooltip": "Unité en secondes",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Salin URL halaman harga",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratis",
|
||||
"Failed to get plans": "Gagal mendapatkan rencana",
|
||||
"Getting started": "Mulai",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Durasi percobaan",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Teks kesalahan",
|
||||
"Error text - Tooltip": "Teks kesalahan",
|
||||
"Is hashed": "Apakah di-hash?",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Sinkronisasi Baru",
|
||||
"Sync interval": "Interval sinkronisasi",
|
||||
"Sync interval - Tooltip": "Satuan dalam detik",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "価格ページのURLをコピー",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "無料",
|
||||
"Failed to get plans": "計画の取得に失敗しました",
|
||||
"Getting started": "はじめる",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "トライアル期間の長さ",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "エラーテキスト",
|
||||
"Error text - Tooltip": "エラーテキスト",
|
||||
"Is hashed": "ハッシュ化されました",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "新しいシンクロナイザー",
|
||||
"Sync interval": "同期の間隔",
|
||||
"Sync interval - Tooltip": "単位は秒です",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "가격 페이지 URL 복사",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "무료",
|
||||
"Failed to get plans": "계획을 가져오지 못했습니다.",
|
||||
"Getting started": "시작하기",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "체험 기간",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "오류 메시지",
|
||||
"Error text - Tooltip": "에러 텍스트",
|
||||
"Is hashed": "해시화 되었습니다",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "신규 싱크어",
|
||||
"Sync interval": "동기화 간격",
|
||||
"Sync interval - Tooltip": "초 단위의 단위",
|
||||
|
@@ -434,17 +434,18 @@
|
||||
"Multi-factor methods": "Métodos 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 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",
|
||||
"Or copy the secret to your Authenticator App": "Or copy the secret to your Authenticator App",
|
||||
"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 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",
|
||||
"Protect your account with Multi-factor authentication": "Proteja sua conta com autenticação de vários fatores",
|
||||
"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",
|
||||
"Setup": "Configuração",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Usar código de verificação SMS",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Sao chép URL trang bảng giá",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Miễn phí",
|
||||
"Failed to get plans": "Falha ao obter planos",
|
||||
"Getting started": "Bắt đầu",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Thời gian thử nghiệm",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Texto de erro",
|
||||
"Error text - Tooltip": "Texto de erro",
|
||||
"Is hashed": "Está criptografado",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Novo Syncer",
|
||||
"Sync interval": "Intervalo de sincronização",
|
||||
"Sync interval - Tooltip": "Unidade em segundos",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Скопировать URL прайс-листа",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Бесплатно",
|
||||
"Failed to get plans": "Не удалось получить планы",
|
||||
"Getting started": "Выьрать план",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Продолжительность пробного периода",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Текст ошибки",
|
||||
"Error text - Tooltip": "Текст ошибки",
|
||||
"Is hashed": "Хешировано",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Новый синхронизатор",
|
||||
"Sync interval": "Интервал синхронизации",
|
||||
"Sync interval - Tooltip": "Единица измерения в секундах",
|
||||
|
@@ -434,17 +434,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",
|
||||
"Multi-factor secret - Tooltip": "Multi-factor secret - Tooltip",
|
||||
"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",
|
||||
"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": "Scan the QR code with your Authenticator App",
|
||||
"Set preferred": "Set preferred",
|
||||
"Setup": "Setup",
|
||||
"Use Authenticator App": "Use Authenticator App",
|
||||
"Use Email": "Use Email",
|
||||
"Use SMS": "Use SMS",
|
||||
"Use SMS verification code": "Use SMS verification code",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "Sao chép URL trang bảng giá",
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Miễn phí",
|
||||
"Failed to get plans": "Không thể lấy được các kế hoạch",
|
||||
"Getting started": "Bắt đầu",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Thời gian thử nghiệm",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "Văn bản lỗi",
|
||||
"Error text - Tooltip": "Văn bản lỗi",
|
||||
"Is hashed": "Đã được băm mã hóa",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer: Đồng bộ mới",
|
||||
"Sync interval": "Khoảng thời gian đồng bộ hóa",
|
||||
"Sync interval - Tooltip": "Đơn vị giây",
|
||||
|
@@ -434,17 +434,18 @@
|
||||
"Multi-factor methods": "多因素认证方式",
|
||||
"Multi-factor recover": "重置多因素认证",
|
||||
"Multi-factor recover description": "如果您无法访问您的设备,输入您的多因素认证恢复代码来确认您的身份",
|
||||
"Multi-factor secret": "多因素密钥",
|
||||
"Multi-factor secret - Tooltip": "多因素密钥 - Tooltip",
|
||||
"Multi-factor secret to clipboard successfully": "多因素密钥已复制到剪贴板",
|
||||
"Or copy the secret to your Authenticator App": "或者将这个密钥复制到你的身份验证应用中",
|
||||
"Passcode": "认证码",
|
||||
"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 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": "通过多因素认证保护您的帐户",
|
||||
"Recovery code": "恢复码",
|
||||
"Scan the QR code with your Authenticator App": "用你的身份验证应用扫描二维码",
|
||||
"Set preferred": "设为首选",
|
||||
"Setup": "设置",
|
||||
"Use Authenticator App": "使用身份验证应用",
|
||||
"Use Email": "使用电子邮件",
|
||||
"Use SMS": "使用短信",
|
||||
"Use SMS verification code": "使用手机或电子邮件发送验证码认证",
|
||||
@@ -568,6 +569,7 @@
|
||||
"Copy pricing page URL": "复制定价页面链接",
|
||||
"Edit Pricing": "编辑定价",
|
||||
"Free": "免费",
|
||||
"Failed to get plans": "未能获取计划",
|
||||
"Getting started": "开始使用",
|
||||
"New Pricing": "添加定价",
|
||||
"Trial duration": "试用期时长",
|
||||
@@ -819,6 +821,8 @@
|
||||
"Error text": "错误信息",
|
||||
"Error text - Tooltip": "错误信息",
|
||||
"Is hashed": "是否参与哈希计算",
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "添加同步器",
|
||||
"Sync interval": "同步间隔",
|
||||
"Sync interval - Tooltip": "单位为秒",
|
||||
|
@@ -64,6 +64,11 @@ class PricingPage extends React.Component {
|
||||
|
||||
Promise.all(plans)
|
||||
.then(results => {
|
||||
const hasError = results.some(result => result.status === "error");
|
||||
if (hasError) {
|
||||
Setting.showMessage("error", `${i18next.t("Failed to get plans")}`);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
plans: results,
|
||||
loading: false,
|
||||
@@ -81,6 +86,11 @@ class PricingPage extends React.Component {
|
||||
|
||||
PricingBackend.getPricing(this.state.owner, pricingName)
|
||||
.then((result) => {
|
||||
if (result.status === "error") {
|
||||
Setting.showMessage("error", result.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: false,
|
||||
pricing: result,
|
||||
|
3021
web/yarn.lock
3021
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user