Fix SEC-03: require token proximity for renewal

- Add 50% lifetime elapsed check to REST handleRenew and gRPC RenewToken
- Reject renewal attempts before 50% of token lifetime has elapsed
- Update existing renewal tests to use short-lived tokens with sleep
- Add TestRenewTokenTooEarly tests for both REST and gRPC

Security: Tokens can only be renewed after 50% of their lifetime has
elapsed, preventing indefinite renewal of stolen tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 00:45:35 -07:00
parent 586d4e3355
commit eef7d1bc1a
5 changed files with 99 additions and 15 deletions

View File

@@ -223,19 +223,20 @@ func TestE2ELoginLogoutFlow(t *testing.T) {
// TestE2ETokenRenewal verifies that renewal returns a new token and revokes the old one.
func TestE2ETokenRenewal(t *testing.T) {
e := newTestEnv(t)
e.createAccount(t, "bob")
acct := e.createAccount(t, "bob")
// Login.
resp := e.do(t, "POST", "/v1/auth/login", map[string]string{
"username": "bob",
"password": "testpass123",
}, "")
mustStatus(t, resp, http.StatusOK)
var lr struct {
Token string `json:"token"`
// Issue a short-lived token (2s) directly so we can wait past the 50%
// renewal threshold (SEC-03) without blocking the test for minutes.
oldToken, claims, err := token.IssueToken(e.privKey, e2eIssuer, acct.UUID, nil, 2*time.Second)
if err != nil {
t.Fatalf("IssueToken: %v", err)
}
decodeJSON(t, resp, &lr)
oldToken := lr.Token
if err := e.db.TrackToken(claims.JTI, acct.ID, claims.IssuedAt, claims.ExpiresAt); err != nil {
t.Fatalf("TrackToken: %v", err)
}
// Wait for >50% of the 2s lifetime to elapse.
time.Sleep(1100 * time.Millisecond)
// Renew.
resp2 := e.do(t, "POST", "/v1/auth/renew", nil, oldToken)