Skip to content

Commit b650823

Browse files
ericfitzclaude
andcommitted
fix(api): handle database validation errors as 400 Bad Request
Add isDBValidationError() helper to detect database validation errors (Oracle ORA-12899, PostgreSQL "value too long", etc.) and return 400 instead of 500 for /admin/groups endpoint. Also add 400 response documentation to /oauth2/revoke and /admin/settings/migrate in OpenAPI spec. Addresses CATS fuzzing errors where Unicode character expansion attacks caused 500 errors instead of proper 400 validation responses. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 252c9a2 commit b650823

File tree

6 files changed

+482
-377
lines changed

6 files changed

+482
-377
lines changed

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"major": 0,
33
"minor": 282,
4-
"patch": 3
4+
"patch": 4
55
}

api-schema/tmi-openapi.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37316,6 +37316,43 @@
3731637316
},
3731737317
"500": {
3731837318
"$ref": "#/components/responses/Error"
37319+
},
37320+
"400": {
37321+
"description": "Invalid request body or parameters",
37322+
"content": {
37323+
"application/json": {
37324+
"schema": {
37325+
"$ref": "#/components/schemas/Error"
37326+
},
37327+
"example": {
37328+
"error": "invalid_request",
37329+
"error_description": "Invalid request body"
37330+
}
37331+
}
37332+
},
37333+
"headers": {
37334+
"X-RateLimit-Limit": {
37335+
"description": "Maximum number of requests allowed in the current time window",
37336+
"schema": {
37337+
"type": "integer",
37338+
"example": 1000
37339+
}
37340+
},
37341+
"X-RateLimit-Remaining": {
37342+
"description": "Number of requests remaining in the current time window",
37343+
"schema": {
37344+
"type": "integer",
37345+
"example": 999
37346+
}
37347+
},
37348+
"X-RateLimit-Reset": {
37349+
"description": "Unix epoch seconds when the rate limit window resets",
37350+
"schema": {
37351+
"type": "integer",
37352+
"example": 1735689600
37353+
}
37354+
}
37355+
}
3731937356
}
3732037357
},
3732137358
"security": [

api/admin_group_handlers.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package api
33
import (
44
"fmt"
55
"net/http"
6+
"strings"
67
"time"
78

89
"github.com/ericfitz/tmi/internal/slogging"
@@ -11,6 +12,39 @@ import (
1112
openapi_types "github.com/oapi-codegen/runtime/types"
1213
)
1314

15+
// isDBValidationError checks if a database error is related to validation
16+
// (e.g., string too long, invalid characters, encoding issues).
17+
// These should be returned as 400 Bad Request rather than 500 Internal Server Error.
18+
func isDBValidationError(err error) bool {
19+
if err == nil {
20+
return false
21+
}
22+
errStr := strings.ToLower(err.Error())
23+
24+
// Oracle errors
25+
if strings.Contains(errStr, "ora-12899") || // Value too large for column
26+
strings.Contains(errStr, "ora-01461") || // Can bind a LONG value only for insert
27+
strings.Contains(errStr, "ora-01704") || // String literal too long
28+
strings.Contains(errStr, "ora-22835") { // Buffer too small
29+
return true
30+
}
31+
32+
// PostgreSQL errors
33+
if strings.Contains(errStr, "value too long") ||
34+
strings.Contains(errStr, "invalid byte sequence") ||
35+
strings.Contains(errStr, "character with byte sequence") {
36+
return true
37+
}
38+
39+
// Generic GORM/SQL errors
40+
if strings.Contains(errStr, "data too long") ||
41+
strings.Contains(errStr, "string data, right truncation") {
42+
return true
43+
}
44+
45+
return false
46+
}
47+
1448
// ListAdminGroups handles GET /admin/groups
1549
func (s *Server) ListAdminGroups(c *gin.Context, params ListAdminGroupsParams) {
1650
logger := slogging.Get().WithContext(c)
@@ -199,6 +233,14 @@ func (s *Server) CreateAdminGroup(c *gin.Context) {
199233
Code: "duplicate_group",
200234
Message: "Group already exists for this provider",
201235
})
236+
} else if isDBValidationError(err) {
237+
// Handle validation errors (e.g., string too long after Unicode expansion)
238+
logger.Warn("Group creation failed due to validation error: %v", err)
239+
HandleRequestError(c, &RequestError{
240+
Status: http.StatusBadRequest,
241+
Code: "validation_error",
242+
Message: "Field value exceeds maximum allowed length or contains invalid characters",
243+
})
202244
} else {
203245
logger.Error("Failed to create group: %v", err)
204246
HandleRequestError(c, &RequestError{

0 commit comments

Comments
 (0)