docs: Separate caller mistakes from backend failures in the plan

createToken's catch-all answered 400 for every unmatched error, so a
database failure reported itself as the caller's malformed request. Found
in review of Task 7.
This commit is contained in:
2026-08-12 14:47:36 +00:00
parent 524ccc6412
commit 4de67e4bea
@@ -495,6 +495,11 @@ 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. It is what lets the handler answer 400 for a bad request and
// 500 for a database that is down, rather than blaming the caller for both.
ErrTokenInvalid = errors.New("invalid token request")
)
// TokenPrefix is on every plaintext so a leaked value is recognisable in a log
@@ -1313,9 +1318,14 @@ 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:
// Anything left is a backend failure. Reporting it as 400 tells the
// caller their request was malformed while the database is down.
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create token"})
return
}
expiry := "no expiry"