Compare commits

..

No commits in common. "2f433472d55665b43dd19fb7961f7c06f75db189" and "a2541b0a5c79d24b6a749c73002fef4851a7a3c4" have entirely different histories.

8 changed files with 202 additions and 263 deletions

View File

@ -57,6 +57,7 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
api := r.Group("api/v1")
api.POST("/login", authMiddleware.LoginHandler)
api.GET("/oauth2/:provider", commonHandler(oauth2redirect))
api.POST("/oauth2/:provider/callback", commonHandler(oauth2callback(authMiddleware)))
optionalAuth := api.Group("", optionalAuthMiddleware(authMiddleware))
optionalAuth.GET("/ws/server", commonHandler(serverStream))
@ -66,8 +67,6 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
optionalAuth.GET("/service/:id", commonHandler(listServiceHistory))
optionalAuth.GET("/service/server", commonHandler(listServerWithServices))
optionalAuth.GET("/oauth2/callback", commonHandler(oauth2callback(authMiddleware)))
optionalAuth.GET("/setting", commonHandler(listConfig))
auth := api.Group("", authMiddleware.MiddlewareFunc())
@ -82,6 +81,7 @@ func routers(r *gin.Engine, frontendDist fs.FS) {
auth.GET("/profile", commonHandler(getProfile))
auth.POST("/profile", commonHandler(updateProfile))
auth.POST("/oauth2/:provider/bind", commonHandler(bindOauth2))
auth.POST("/oauth2/:provider/unbind", commonHandler(unbindOauth2))
auth.GET("/user", adminHandler(listUser))

View File

@ -89,7 +89,6 @@ func authenticator() func(c *gin.Context) (interface{}, error) {
var user model.User
realip := c.GetString(model.CtxKeyRealIPStr)
if err := singleton.DB.Select("id", "password").Where("username = ?", loginVals.Username).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeLoginFail, model.BlockIDUnknownUser)
@ -97,11 +96,6 @@ func authenticator() func(c *gin.Context) (interface{}, error) {
return nil, jwt.ErrFailedAuthentication
}
if user.RejectPassword {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeLoginFail, int64(user.ID))
return nil, jwt.ErrFailedAuthentication
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(loginVals.Password)); err != nil {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeLoginFail, int64(user.ID))
return nil, jwt.ErrFailedAuthentication

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
@ -21,13 +20,27 @@ import (
"github.com/nezhahq/nezha/service/singleton"
)
func getRedirectURL(c *gin.Context) string {
type Oauth2LoginType uint8
const (
_ Oauth2LoginType = iota
rTypeLogin
rTypeBind
)
func getRedirectURL(c *gin.Context, provider string, rType Oauth2LoginType) string {
scheme := "http://"
referer := c.Request.Referer()
if forwardedProto := c.Request.Header.Get("X-Forwarded-Proto"); forwardedProto == "https" || strings.HasPrefix(referer, "https://") {
scheme = "https://"
}
return scheme + c.Request.Host + "/api/v1/oauth2/callback"
var suffix string
if rType == rTypeLogin {
suffix = "/dashboard/login?provider=" + provider
} else if rType == rTypeBind {
suffix = "/dashboard/profile?provider=" + provider
}
return scheme + c.Request.Host + suffix
}
// @Summary Get Oauth2 Redirect URL
@ -43,7 +56,7 @@ func oauth2redirect(c *gin.Context) (*model.Oauth2LoginResponse, error) {
return nil, singleton.Localizer.ErrorT("provider is required")
}
rTypeInt, err := strconv.ParseUint(c.Query("type"), 10, 8)
rTypeInt, err := strconv.Atoi(c.Query("type"))
if err != nil {
return nil, err
}
@ -52,18 +65,14 @@ func oauth2redirect(c *gin.Context) (*model.Oauth2LoginResponse, error) {
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
o2conf := o2confRaw.Setup(getRedirectURL(c))
o2conf := o2confRaw.Setup(getRedirectURL(c, provider, Oauth2LoginType(rTypeInt)))
randomString, err := utils.GenerateRandomString(32)
if err != nil {
return nil, err
}
state, stateKey := randomString[:16], randomString[16:]
singleton.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey), &model.Oauth2State{
Action: model.Oauth2LoginType(rTypeInt),
Provider: provider,
State: state,
}, cache.DefaultExpiration)
singleton.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey), state, cache.DefaultExpiration)
url := o2conf.AuthCodeURL(state, oauth2.AccessTypeOnline)
c.SetCookie("nz-o2s", stateKey, 60*5, "", "", false, false)
@ -71,125 +80,19 @@ func oauth2redirect(c *gin.Context) (*model.Oauth2LoginResponse, error) {
return &model.Oauth2LoginResponse{Redirect: url}, nil
}
// @Summary Unbind Oauth2
// @Description Unbind Oauth2
// @Accept json
// @Produce json
// @Param provider path string true "provider"
// @Success 200 {object} any
// @Router /api/v1/oauth2/{provider}/unbind [post]
func unbindOauth2(c *gin.Context) (any, error) {
provider := c.Param("provider")
if provider == "" {
return nil, singleton.Localizer.ErrorT("provider is required")
}
_, has := singleton.Conf.Oauth2[provider]
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
provider = strings.ToLower(provider)
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
query := singleton.DB.Where("provider = ? AND user_id = ?", provider, u.ID)
var bindCount int64
if err := query.Model(&model.Oauth2Bind{}).Count(&bindCount).Error; err != nil {
return nil, newGormError("%v", err)
}
if bindCount < 2 && u.RejectPassword {
return nil, singleton.Localizer.ErrorT("operation not permitted")
}
if err := query.Delete(&model.Oauth2Bind{}).Error; err != nil {
return nil, newGormError("%v", err)
}
return nil, nil
}
// @Summary Oauth2 Callback
// @Description Oauth2 Callback
// @Accept json
// @Produce json
// @Param state query string true "state"
// @Param code query string true "code"
// @Success 200 {object} model.LoginResponse
// @Router /api/v1/oauth2/callback [get]
func oauth2callback(jwtConfig *jwt.GinJWTMiddleware) func(c *gin.Context) (*model.LoginResponse, error) {
return func(c *gin.Context) (*model.LoginResponse, error) {
callbackData := &model.Oauth2Callback{
State: c.Query("state"),
Code: c.Query("code"),
}
state, err := verifyState(c, callbackData.State)
func exchangeOpenId(c *gin.Context, o2confRaw *model.Oauth2Config, provider string, callbackData model.Oauth2Callback, typ Oauth2LoginType) (string, error) {
// 验证登录跳转时的 State
stateKey, err := c.Cookie("nz-o2s")
if err != nil {
return nil, err
return "", singleton.Localizer.ErrorT("invalid state key")
}
state, ok := singleton.Cache.Get(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey))
if !ok || state.(string) != callbackData.State {
return "", singleton.Localizer.ErrorT("invalid state key")
}
o2confRaw, has := singleton.Conf.Oauth2[state.Provider]
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
o2conf := o2confRaw.Setup(getRedirectURL(c, provider, typ))
realip := c.GetString(model.CtxKeyRealIPStr)
if callbackData.Code == "" {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeBruteForceOauth2, model.BlockIDToken)
return nil, singleton.Localizer.ErrorT("code is required")
}
openId, err := exchangeOpenId(c, o2confRaw, callbackData)
if err != nil {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeBruteForceOauth2, model.BlockIDToken)
return nil, err
}
var bind model.Oauth2Bind
switch state.Action {
case model.RTypeBind:
u, authorized := c.Get(model.CtxKeyAuthorizedUser)
if !authorized {
return nil, singleton.Localizer.ErrorT("unauthorized")
}
user := u.(*model.User)
result := singleton.DB.Where("provider = ? AND open_id = ?", strings.ToLower(state.Provider), openId).Limit(1).Find(&bind)
if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
return nil, newGormError("%v", result.Error)
}
bind.UserID = user.ID
bind.Provider = state.Provider
bind.OpenID = openId
if result.Error == gorm.ErrRecordNotFound {
result = singleton.DB.Create(&bind)
} else {
result = singleton.DB.Save(&bind)
}
if result.Error != nil {
return nil, newGormError("%v", result.Error)
}
default:
if err := singleton.DB.Where("provider = ? AND open_id = ?", strings.ToLower(state.Provider), openId).First(&bind).Error; err != nil {
return nil, singleton.Localizer.ErrorT("oauth2 user not binded yet")
}
}
tokenString, expire, err := jwtConfig.TokenGenerator(fmt.Sprintf("%d", bind.UserID))
if err != nil {
return nil, err
}
jwtConfig.SetCookie(c, tokenString)
c.Redirect(http.StatusFound, utils.IfOr(state.Action == model.RTypeBind, "/dashboard/profile?oauth2=true", "/dashboard/login?oauth2=true"))
return &model.LoginResponse{Token: tokenString, Expire: expire.Format(time.RFC3339)}, nil
}
}
func exchangeOpenId(c *gin.Context, o2confRaw *model.Oauth2Config, callbackData *model.Oauth2Callback) (string, error) {
o2conf := o2confRaw.Setup(getRedirectURL(c))
ctx := context.Background()
otk, err := o2conf.Exchange(ctx, callbackData.Code)
@ -210,23 +113,127 @@ func exchangeOpenId(c *gin.Context, o2confRaw *model.Oauth2Config, callbackData
return gjson.GetBytes(body, o2confRaw.UserIDPath).String(), nil
}
func verifyState(c *gin.Context, state string) (*model.Oauth2State, error) {
// 验证登录跳转时的 State
stateKey, err := c.Cookie("nz-o2s")
// @Summary Oauth2 Callback
// @Description Oauth2 Callback
// @Accept json
// @Produce json
// @Param provider path string true "provider"
// @Param body body model.Oauth2Callback true "body"
// @Success 200 {object} model.LoginResponse
// @Router /api/v1/oauth2/{provider}/callback [post]
func oauth2callback(jwtConfig *jwt.GinJWTMiddleware) func(c *gin.Context) (*model.LoginResponse, error) {
return func(c *gin.Context) (*model.LoginResponse, error) {
provider := c.Param("provider")
if provider == "" {
return nil, singleton.Localizer.ErrorT("provider is required")
}
o2confRaw, has := singleton.Conf.Oauth2[provider]
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
var callbackData model.Oauth2Callback
if err := c.ShouldBind(&callbackData); err != nil {
return nil, err
}
realip := c.GetString(model.CtxKeyRealIPStr)
if callbackData.Code == "" {
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeBruteForceOauth2, model.BlockIDToken)
return nil, singleton.Localizer.ErrorT("code is required")
}
openId, err := exchangeOpenId(c, o2confRaw, provider, callbackData, rTypeLogin)
if err != nil {
return nil, singleton.Localizer.ErrorT("invalid state key")
model.BlockIP(singleton.DB, realip, model.WAFBlockReasonTypeBruteForceOauth2, model.BlockIDToken)
return nil, err
}
cacheKey := fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey)
istate, ok := singleton.Cache.Get(cacheKey)
if !ok {
return nil, singleton.Localizer.ErrorT("invalid state key")
var bind model.Oauth2Bind
if err := singleton.DB.Where("provider = ? AND open_id = ?", strings.ToLower(provider), openId).First(&bind).Error; err != nil {
return nil, singleton.Localizer.ErrorT("oauth2 user not binded yet")
}
oauth2State, ok := istate.(*model.Oauth2State)
if !ok || oauth2State.State != state {
return nil, singleton.Localizer.ErrorT("invalid state key")
tokenString, expire, err := jwtConfig.TokenGenerator(fmt.Sprintf("%d", bind.UserID))
if err != nil {
return nil, err
}
return oauth2State, nil
jwtConfig.SetCookie(c, tokenString)
return &model.LoginResponse{Token: tokenString, Expire: expire.Format(time.RFC3339)}, nil
}
}
// @Summary Bind Oauth2
// @Description Bind Oauth2
// @Accept json
// @Produce json
// @Param provider path string true "provider"
// @Param body body model.Oauth2Callback true "body"
// @Success 200 {object} any
// @Router /api/v1/oauth2/{provider}/bind [post]
func bindOauth2(c *gin.Context) (any, error) {
var bindData model.Oauth2Callback
if err := c.ShouldBind(&bindData); err != nil {
return nil, err
}
provider := c.Param("provider")
o2conf, has := singleton.Conf.Oauth2[provider]
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
openId, err := exchangeOpenId(c, o2conf, provider, bindData, rTypeBind)
if err != nil {
return nil, err
}
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
provider = strings.ToLower(provider)
var bind model.Oauth2Bind
result := singleton.DB.Where("provider = ? AND open_id = ?", provider, openId).Limit(1).Find(&bind)
if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
return nil, newGormError("%v", result.Error)
}
bind.UserID = u.ID
bind.Provider = provider
bind.OpenID = openId
if result.Error == gorm.ErrRecordNotFound {
result = singleton.DB.Create(&bind)
} else {
result = singleton.DB.Save(&bind)
}
if result.Error != nil {
return nil, newGormError("%v", result.Error)
}
return nil, nil
}
// @Summary Unbind Oauth2
// @Description Unbind Oauth2
// @Accept json
// @Produce json
// @Param provider path string true "provider"
// @Success 200 {object} any
// @Router /api/v1/oauth2/{provider}/unbind [post]
func unbindOauth2(c *gin.Context) (any, error) {
provider := c.Param("provider")
if provider == "" {
return nil, singleton.Localizer.ErrorT("provider is required")
}
_, has := singleton.Conf.Oauth2[provider]
if !has {
return nil, singleton.Localizer.ErrorT("provider not found")
}
provider = strings.ToLower(provider)
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
if err := singleton.DB.Where("provider = ? AND user_id = ?", provider, u.ID).Delete(&model.Oauth2Bind{}).Error; err != nil {
return nil, newGormError("%v", err)
}
return nil, nil
}

View File

@ -73,18 +73,8 @@ func updateProfile(c *gin.Context) (any, error) {
return nil, err
}
var bindCount int64
if err := singleton.DB.Where("user_id = ?", auth.(*model.User).ID).Count(&bindCount).Error; err != nil {
return nil, newGormError("%v", err)
}
if pf.RejectPassword && bindCount < 1 {
return nil, singleton.Localizer.ErrorT("you don't have any oauth2 bindings")
}
user.Username = pf.NewUsername
user.Password = string(hash)
user.RejectPassword = pf.RejectPassword
if err := singleton.DB.Save(&user).Error; err != nil {
return nil, newGormError("%v", err)
}

View File

@ -7,17 +7,3 @@ type Oauth2Bind struct {
Provider string `gorm:"uniqueIndex:u_p_o" json:"provider,omitempty"`
OpenID string `gorm:"uniqueIndex:u_p_o" json:"open_id,omitempty"`
}
type Oauth2LoginType uint8
const (
_ Oauth2LoginType = iota
RTypeLogin
RTypeBind
)
type Oauth2State struct {
Action Oauth2LoginType
Provider string
State string
}

View File

@ -19,7 +19,6 @@ type User struct {
Password string `json:"password,omitempty" gorm:"type:char(72)"`
Role uint8 `json:"role,omitempty"`
AgentSecret string `json:"agent_secret,omitempty" gorm:"type:char(32)"`
RejectPassword bool `json:"reject_password,omitempty"`
}
type UserInfo struct {

View File

@ -10,5 +10,4 @@ type ProfileForm struct {
OriginalPassword string `json:"original_password,omitempty"`
NewUsername string `json:"new_username,omitempty"`
NewPassword string `json:"new_password,omitempty"`
RejectPassword bool `json:"reject_password,omitempty"`
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-31 21:25+0800\n"
"POT-Creation-Date: 2024-12-22 11:55+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,90 +17,89 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: cmd/dashboard/controller/alertrule.go:104
#: cmd/dashboard/controller/alertrule.go:103
#, c-format
msgid "alert id %d does not exist"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:108
#: cmd/dashboard/controller/alertrule.go:156
#: cmd/dashboard/controller/alertrule.go:176
#: cmd/dashboard/controller/controller.go:216
#: cmd/dashboard/controller/cron.go:58 cmd/dashboard/controller/cron.go:124
#: cmd/dashboard/controller/cron.go:136 cmd/dashboard/controller/cron.go:195
#: cmd/dashboard/controller/cron.go:224 cmd/dashboard/controller/ddns.go:131
#: cmd/dashboard/controller/ddns.go:192 cmd/dashboard/controller/fm.go:43
#: cmd/dashboard/controller/nat.go:59 cmd/dashboard/controller/nat.go:110
#: cmd/dashboard/controller/nat.go:121 cmd/dashboard/controller/nat.go:160
#: cmd/dashboard/controller/notification.go:112
#: cmd/dashboard/controller/notification.go:166
#: cmd/dashboard/controller/alertrule.go:107
#: cmd/dashboard/controller/alertrule.go:155
#: cmd/dashboard/controller/alertrule.go:175
#: cmd/dashboard/controller/controller.go:211
#: cmd/dashboard/controller/cron.go:57 cmd/dashboard/controller/cron.go:123
#: cmd/dashboard/controller/cron.go:135 cmd/dashboard/controller/cron.go:194
#: cmd/dashboard/controller/cron.go:223 cmd/dashboard/controller/ddns.go:130
#: cmd/dashboard/controller/ddns.go:191 cmd/dashboard/controller/fm.go:43
#: cmd/dashboard/controller/nat.go:58 cmd/dashboard/controller/nat.go:109
#: cmd/dashboard/controller/nat.go:120 cmd/dashboard/controller/nat.go:159
#: cmd/dashboard/controller/notification.go:111
#: cmd/dashboard/controller/notification.go:165
#: cmd/dashboard/controller/notification_group.go:76
#: cmd/dashboard/controller/notification_group.go:152
#: cmd/dashboard/controller/notification_group.go:164
#: cmd/dashboard/controller/notification_group.go:233
#: cmd/dashboard/controller/server.go:65 cmd/dashboard/controller/server.go:77
#: cmd/dashboard/controller/server.go:128
#: cmd/dashboard/controller/server.go:192
#: cmd/dashboard/controller/server.go:64 cmd/dashboard/controller/server.go:76
#: cmd/dashboard/controller/server.go:127
#: cmd/dashboard/controller/server.go:191
#: cmd/dashboard/controller/server_group.go:75
#: cmd/dashboard/controller/server_group.go:150
#: cmd/dashboard/controller/server_group.go:229
#: cmd/dashboard/controller/service.go:273
#: cmd/dashboard/controller/service.go:344
#: cmd/dashboard/controller/service.go:371
#: cmd/dashboard/controller/service.go:272
#: cmd/dashboard/controller/service.go:343
#: cmd/dashboard/controller/service.go:370
#: cmd/dashboard/controller/terminal.go:41
msgid "permission denied"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:184
#: cmd/dashboard/controller/alertrule.go:183
msgid "duration need to be at least 3"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:188
#: cmd/dashboard/controller/alertrule.go:187
msgid "cycle_interval need to be at least 1"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:191
#: cmd/dashboard/controller/alertrule.go:190
msgid "cycle_start is not set"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:194
#: cmd/dashboard/controller/alertrule.go:193
msgid "cycle_start is a future value"
msgstr ""
#: cmd/dashboard/controller/alertrule.go:199
#: cmd/dashboard/controller/alertrule.go:198
msgid "need to configure at least a single rule"
msgstr ""
#: cmd/dashboard/controller/controller.go:210
#: cmd/dashboard/controller/oauth2.go:152
#: cmd/dashboard/controller/controller.go:205
#: cmd/dashboard/controller/server_group.go:162
#: cmd/dashboard/controller/service.go:96 cmd/dashboard/controller/user.go:27
#: cmd/dashboard/controller/user.go:63
#: cmd/dashboard/controller/service.go:95 cmd/dashboard/controller/user.go:26
#: cmd/dashboard/controller/user.go:53
msgid "unauthorized"
msgstr ""
#: cmd/dashboard/controller/controller.go:233
#: cmd/dashboard/controller/controller.go:228
msgid "database error"
msgstr ""
#: cmd/dashboard/controller/cron.go:75 cmd/dashboard/controller/cron.go:149
#: cmd/dashboard/controller/cron.go:74 cmd/dashboard/controller/cron.go:148
msgid "scheduled tasks cannot be triggered by alarms"
msgstr ""
#: cmd/dashboard/controller/cron.go:132 cmd/dashboard/controller/cron.go:190
#: cmd/dashboard/controller/cron.go:131 cmd/dashboard/controller/cron.go:189
#, c-format
msgid "task id %d does not exist"
msgstr ""
#: cmd/dashboard/controller/ddns.go:57 cmd/dashboard/controller/ddns.go:122
#: cmd/dashboard/controller/ddns.go:56 cmd/dashboard/controller/ddns.go:121
msgid "the retry count must be an integer between 1 and 10"
msgstr ""
#: cmd/dashboard/controller/ddns.go:81 cmd/dashboard/controller/ddns.go:154
#: cmd/dashboard/controller/ddns.go:80 cmd/dashboard/controller/ddns.go:153
msgid "error parsing %s: %v"
msgstr ""
#: cmd/dashboard/controller/ddns.go:127 cmd/dashboard/controller/nat.go:117
#: cmd/dashboard/controller/ddns.go:126 cmd/dashboard/controller/nat.go:116
#, c-format
msgid "profile id %d does not exist"
msgstr ""
@ -109,12 +108,12 @@ msgstr ""
msgid "server not found or not connected"
msgstr ""
#: cmd/dashboard/controller/notification.go:69
#: cmd/dashboard/controller/notification.go:131
#: cmd/dashboard/controller/notification.go:68
#: cmd/dashboard/controller/notification.go:130
msgid "a test message"
msgstr ""
#: cmd/dashboard/controller/notification.go:108
#: cmd/dashboard/controller/notification.go:107
#, c-format
msgid "notification id %d does not exist"
msgstr ""
@ -130,34 +129,7 @@ msgstr ""
msgid "group id %d does not exist"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:42 cmd/dashboard/controller/oauth2.go:83
msgid "provider is required"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:52 cmd/dashboard/controller/oauth2.go:87
#: cmd/dashboard/controller/oauth2.go:132
msgid "provider not found"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:100
msgid "operation not permitted"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:138
msgid "code is required"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:174
msgid "oauth2 user not binded yet"
msgstr ""
#: cmd/dashboard/controller/oauth2.go:215
#: cmd/dashboard/controller/oauth2.go:221
#: cmd/dashboard/controller/oauth2.go:226
msgid "invalid state key"
msgstr ""
#: cmd/dashboard/controller/server.go:73
#: cmd/dashboard/controller/server.go:72
#, c-format
msgid "server id %d does not exist"
msgstr ""
@ -167,37 +139,29 @@ msgstr ""
msgid "have invalid server id"
msgstr ""
#: cmd/dashboard/controller/service.go:89
#: cmd/dashboard/controller/service.go:165
#: cmd/dashboard/controller/service.go:88
#: cmd/dashboard/controller/service.go:164
msgid "server not found"
msgstr ""
#: cmd/dashboard/controller/service.go:269
#: cmd/dashboard/controller/service.go:268
#, c-format
msgid "service id %d does not exist"
msgstr ""
#: cmd/dashboard/controller/user.go:68
#: cmd/dashboard/controller/user.go:58
msgid "incorrect password"
msgstr ""
#: cmd/dashboard/controller/user.go:82
msgid "you don't have any oauth2 bindings"
msgstr ""
#: cmd/dashboard/controller/user.go:130
#: cmd/dashboard/controller/user.go:110
msgid "password length must be greater than 6"
msgstr ""
#: cmd/dashboard/controller/user.go:133
#: cmd/dashboard/controller/user.go:113
msgid "username can't be empty"
msgstr ""
#: cmd/dashboard/controller/user.go:136
msgid "invalid role"
msgstr ""
#: cmd/dashboard/controller/user.go:175
#: cmd/dashboard/controller/user.go:152
msgid "can't delete yourself"
msgstr ""
@ -225,11 +189,11 @@ msgstr ""
msgid "IP Changed"
msgstr ""
#: service/singleton/alertsentinel.go:170
#: service/singleton/alertsentinel.go:167
msgid "Incident"
msgstr ""
#: service/singleton/alertsentinel.go:180
#: service/singleton/alertsentinel.go:177
msgid "Resolved"
msgstr ""
@ -295,6 +259,6 @@ msgstr ""
msgid "Down"
msgstr ""
#: service/singleton/user.go:60
#: service/singleton/user.go:53
msgid "user id not specified"
msgstr ""