fix: Distinguish caller mistakes from backend failures in CreateAPIToken
createToken's catch-all mapped every unmatched error to 400, so a database outage reported itself as a malformed client request. Wrap the genuine validation failures with ErrTokenInvalid and let the handler answer 500 with a fixed message for everything else.
This commit is contained in:
@@ -62,9 +62,12 @@ func createToken(c *gin.Context) {
|
||||
case errors.Is(err, services.ErrInvalidScope):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "code": "invalid_scope"})
|
||||
return
|
||||
case err != nil:
|
||||
case errors.Is(err, services.ErrTokenInvalid):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
case err != nil:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create token"})
|
||||
return
|
||||
}
|
||||
|
||||
expiry := "no expiry"
|
||||
|
||||
@@ -20,6 +20,9 @@ var (
|
||||
ErrTokenNameTaken = errors.New("a token with that name already exists")
|
||||
ErrTokenRoleTooHigh = errors.New("cannot create a token above your own role")
|
||||
ErrTokenExpiryPolicy = errors.New("expiry exceeds this instance's maximum token lifetime")
|
||||
// ErrTokenInvalid marks a caller mistake as distinct from a backend
|
||||
// failure, which is what lets the handler choose 400 or 500.
|
||||
ErrTokenInvalid = errors.New("invalid token request")
|
||||
)
|
||||
|
||||
// TokenPrefix is on every plaintext so a leaked value is recognisable in a log
|
||||
@@ -58,10 +61,10 @@ func LowerRole(a, b string) string {
|
||||
func CreateAPIToken(instanceID, userID, name, role string, scopes []string, expiresInDays *int, ip string) (*models.APIToken, string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || len(name) > tokenNameMax {
|
||||
return nil, "", fmt.Errorf("token name must be 1 to %d characters", tokenNameMax)
|
||||
return nil, "", fmt.Errorf("%w: token name must be 1 to %d characters", ErrTokenInvalid, tokenNameMax)
|
||||
}
|
||||
if !models.ValidRole(role) {
|
||||
return nil, "", fmt.Errorf("invalid role %q", role)
|
||||
return nil, "", fmt.Errorf("%w: invalid role %q", ErrTokenInvalid, role)
|
||||
}
|
||||
if err := ValidScopes(scopes); err != nil {
|
||||
return nil, "", err
|
||||
@@ -69,7 +72,7 @@ func CreateAPIToken(instanceID, userID, name, role string, scopes []string, expi
|
||||
|
||||
owner, err := GetUserInInstance(instanceID, userID)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("user not found")
|
||||
return nil, "", fmt.Errorf("%w: user not found", ErrTokenInvalid)
|
||||
}
|
||||
if roleRank(role) > roleRank(owner.Role) {
|
||||
return nil, "", ErrTokenRoleTooHigh
|
||||
@@ -85,7 +88,7 @@ func CreateAPIToken(instanceID, userID, name, role string, scopes []string, expi
|
||||
switch {
|
||||
case expiresInDays != nil:
|
||||
if *expiresInDays <= 0 {
|
||||
return nil, "", fmt.Errorf("expires_in_days must be positive")
|
||||
return nil, "", fmt.Errorf("%w: expires_in_days must be positive", ErrTokenInvalid)
|
||||
}
|
||||
if maxDays > 0 && *expiresInDays > maxDays {
|
||||
return nil, "", fmt.Errorf("%w: maximum is %d day(s)", ErrTokenExpiryPolicy, maxDays)
|
||||
|
||||
Reference in New Issue
Block a user