Merge SEC-03: require token proximity for renewal

# Conflicts:
#	internal/server/server_test.go
This commit is contained in:
2026-03-13 01:07:34 -07:00
5 changed files with 99 additions and 15 deletions

View File

@@ -563,7 +563,8 @@ func TestRenewToken(t *testing.T) {
acct := createTestHumanAccount(t, srv, "renew-user")
handler := srv.Handler()
oldTokenStr, claims, err := token.IssueToken(priv, testIssuer, acct.UUID, nil, time.Hour)
// Issue a short-lived token (2s) so we can wait past the 50% threshold.
oldTokenStr, claims, err := token.IssueToken(priv, testIssuer, acct.UUID, nil, 2*time.Second)
if err != nil {
t.Fatalf("IssueToken: %v", err)
}
@@ -572,6 +573,9 @@ func TestRenewToken(t *testing.T) {
t.Fatalf("TrackToken: %v", err)
}
// Wait for >50% of the 2s lifetime to elapse.
time.Sleep(1100 * time.Millisecond)
rr := doRequest(t, handler, "POST", "/v1/auth/renew", nil, oldTokenStr)
if rr.Code != http.StatusOK {
t.Fatalf("renew status = %d, want 200; body: %s", rr.Code, rr.Body.String())
@@ -729,3 +733,29 @@ func TestLoginLockedAccountReturns401(t *testing.T) {
t.Errorf("locked response error = %q, want %q", lockedBody.Error, "invalid credentials")
}
}
// TestRenewTokenTooEarly verifies that a token cannot be renewed before 50%
// of its lifetime has elapsed (SEC-03).
func TestRenewTokenTooEarly(t *testing.T) {
srv, _, priv, _ := newTestServer(t)
acct := createTestHumanAccount(t, srv, "renew-early-user")
handler := srv.Handler()
// Issue a long-lived token so 50% is far in the future.
tokStr, claims, err := token.IssueToken(priv, testIssuer, acct.UUID, nil, time.Hour)
if err != nil {
t.Fatalf("IssueToken: %v", err)
}
if err := srv.db.TrackToken(claims.JTI, acct.ID, claims.IssuedAt, claims.ExpiresAt); err != nil {
t.Fatalf("TrackToken: %v", err)
}
// Immediately try to renew — should be rejected.
rr := doRequest(t, handler, "POST", "/v1/auth/renew", nil, tokStr)
if rr.Code != http.StatusBadRequest {
t.Fatalf("renew status = %d, want 400; body: %s", rr.Code, rr.Body.String())
}
if !strings.Contains(rr.Body.String(), "not yet eligible for renewal") {
t.Errorf("expected eligibility message, got: %s", rr.Body.String())
}
}