Skip to content

Commit a1f83c5

Browse files
committed
fix: enforce sort_key is unique per user
1 parent 9942563 commit a1f83c5

4 files changed

Lines changed: 31 additions & 4 deletions

File tree

‎api/application.go‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/gotify/server/v2/auth"
1313
"github.com/gotify/server/v2/model"
1414
"github.com/h2non/filetype"
15+
"gorm.io/gorm"
1516
)
1617

1718
// The ApplicationDatabase interface for encapsulating database access.
@@ -101,7 +102,8 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) {
101102
Internal: false,
102103
}
103104

104-
if success := successOrAbort(ctx, 500, a.DB.CreateApplication(&app)); !success {
105+
if err := a.DB.CreateApplication(&app); err != nil {
106+
handleApplicationError(ctx, err)
105107
return
106108
}
107109
ctx.JSON(200, withResolvedImage(&app))
@@ -261,7 +263,8 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) {
261263
app.SortKey = applicationParams.SortKey
262264
}
263265

264-
if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success {
266+
if err := a.DB.UpdateApplication(app); err != nil {
267+
handleApplicationError(ctx, err)
265268
return
266269
}
267270
ctx.JSON(200, withResolvedImage(app))
@@ -476,3 +479,11 @@ func ValidApplicationImageExt(ext string) bool {
476479
return false
477480
}
478481
}
482+
483+
func handleApplicationError(ctx *gin.Context, err error) {
484+
if errors.Is(err, gorm.ErrDuplicatedKey) {
485+
ctx.AbortWithError(400, errors.New("sort key is not unique"))
486+
} else {
487+
ctx.AbortWithError(500, err)
488+
}
489+
}

‎api/application_test.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,21 @@ func (s *ApplicationSuite) Test_UpdateApplication_WithoutPermission_expectNotFou
647647
assert.Equal(s.T(), 404, s.recorder.Code)
648648
}
649649

650+
func (s *ApplicationSuite) Test_UpdateApplication_duplicateSortKey() {
651+
user := s.db.User(5)
652+
user.App(1) // sortKey=a0
653+
user.App(2) // sortKey=a1
654+
655+
s.withFormData("name=new_name&sortKey=a0")
656+
test.WithUser(s.ctx, 5)
657+
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
658+
659+
s.a.UpdateApplication(s.ctx)
660+
661+
assert.EqualError(s.T(), s.ctx.Errors[0].Err, "sort key is not unique")
662+
assert.Equal(s.T(), 400, s.recorder.Code)
663+
}
664+
650665
func (s *ApplicationSuite) withFormData(formData string) {
651666
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(formData))
652667
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

‎database/database.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre
3636
gormConfig := &gorm.Config{
3737
Logger: dbLogger,
3838
DisableForeignKeyConstraintWhenMigrating: true,
39+
TranslateError: true,
3940
}
4041

4142
var db *gorm.DB

‎model/application.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Application struct {
2020
// required: true
2121
// example: AWH0wZ5r0Mbac.r
2222
Token string `gorm:"type:varchar(180);uniqueIndex:uix_applications_token" json:"token"`
23-
UserID uint `gorm:"index" json:"-"`
23+
UserID uint `gorm:"index;uniqueIndex:uix_application_user_id_sort_key,priority:1" json:"-"`
2424
// The application name. This is how the application should be displayed to the user.
2525
//
2626
// required: true
@@ -58,5 +58,5 @@ type Application struct {
5858
//
5959
// required: true
6060
// example: a1
61-
SortKey string `form:"sortKey" query:"sortKey" json:"sortKey"`
61+
SortKey string `gorm:"uniqueIndex:uix_application_user_id_sort_key,priority:2" form:"sortKey" query:"sortKey" json:"sortKey"`
6262
}

0 commit comments

Comments
 (0)