diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index 82eda8f..241e236 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -45,6 +45,7 @@ type Operations interface { Create(ctx context.Context, smap model.Operation) (model.Operation, error) Update(ctx context.Context, id int32, values OptionalMap) error Delete(ctx context.Context, id int32) error + CountSince(ctx context.Context, owner int64, since int64) (int64, error) } type Submissions interface { diff --git a/pkg/datastore/gormstore/operations.go b/pkg/datastore/gormstore/operations.go index bf49c86..87b456f 100644 --- a/pkg/datastore/gormstore/operations.go +++ b/pkg/datastore/gormstore/operations.go @@ -53,3 +53,12 @@ func (env *Operations) Delete(ctx context.Context, id int32) error { return nil } + +func (env *Operations) CountSince(ctx context.Context, owner int64, since int64) (int64, error) { + var count int64 + if err := env.db.Where("owner = ? AND created_at > ?",owner,since).Count(&count).Error; err != nil { + return count, err + } + + return count, nil +} diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index a0e9e02..17fe5e0 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -27,12 +27,16 @@ var( model.MapfixStatusValidating, model.MapfixStatusAccepted, } + // Allow 5 mapfixes every 10 minutes + CreateMapfixRateLimit int64 = 5 + CreateMapfixRecencyWindow = time.Second*600 ) var ( ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20") ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID") ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied) + ErrCreateMapfixRateLimit = errors.New("You must not create more than 5 mapfixes every 10 minutes") ) // POST /mapfixes @@ -76,6 +80,21 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTrigger } } + // Check if too many operations have been created recently + { + count, err := svc.DB.Operations().CountSince(ctx, + int64(userId), + time.Now().Add(-CreateMapfixRecencyWindow).Unix(), + ) + if err != nil { + return nil, err + } + + if CreateMapfixRateLimit < count { + return nil, ErrCreateMapfixRateLimit + } + } + operation, err := svc.DB.Operations().Create(ctx, model.Operation{ Owner: int64(userId), StatusID: model.OperationStatusCreated, diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index ea89032..c9a6a80 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -27,6 +27,9 @@ var( model.SubmissionStatusValidating, model.SubmissionStatusAcceptedUnvalidated, } + // Allow 5 submissions every 10 minutes + CreateSubmissionRateLimit int64 = 5 + CreateSubmissionRecencyWindow = time.Second*600 ) var ( @@ -35,6 +38,7 @@ var ( ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released") ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released") ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied) + ErrCreateSubmissionRateLimit = errors.New("You must not create more than 5 submissions every 10 minutes") ) // POST /submissions @@ -66,6 +70,22 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio return nil, ErrCreationPhaseSubmissionsLimit } } + + // Check if too many operations have been created recently + { + count, err := svc.DB.Operations().CountSince(ctx, + int64(userId), + time.Now().Add(-CreateSubmissionRecencyWindow).Unix(), + ) + if err != nil { + return nil, err + } + + if CreateSubmissionRateLimit < count { + return nil, ErrCreateSubmissionRateLimit + } + } + operation, err := svc.DB.Operations().Create(ctx, model.Operation{ Owner: int64(userId), StatusID: model.OperationStatusCreated,