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

@@ -12,6 +12,7 @@ import (
"io"
"log/slog"
"net"
"strings"
"testing"
"time"
@@ -144,7 +145,12 @@ func (e *testEnv) issueAdminToken(t *testing.T, username string) (string, *model
// issueUserToken issues a regular (non-admin) token for an account.
func (e *testEnv) issueUserToken(t *testing.T, acct *model.Account) string {
t.Helper()
tokenStr, claims, err := token.IssueToken(e.priv, testIssuer, acct.UUID, []string{}, time.Hour)
return e.issueShortToken(t, acct, time.Hour)
}
func (e *testEnv) issueShortToken(t *testing.T, acct *model.Account, expiry time.Duration) string {
t.Helper()
tokenStr, claims, err := token.IssueToken(e.priv, testIssuer, acct.UUID, []string{}, expiry)
if err != nil {
t.Fatalf("issue token: %v", err)
}
@@ -358,11 +364,17 @@ func TestLogout(t *testing.T) {
}
}
// TestRenewToken verifies that a valid token can be renewed.
// TestRenewToken verifies that a valid token can be renewed after 50% of its
// lifetime has elapsed (SEC-03).
func TestRenewToken(t *testing.T) {
e := newTestEnv(t)
acct := e.createHumanAccount(t, "renewuser")
tok := e.issueUserToken(t, acct)
// Issue a short-lived token (2s) so we can wait past the 50% threshold.
tok := e.issueShortToken(t, acct, 2*time.Second)
// Wait for >50% of lifetime to elapse.
time.Sleep(1100 * time.Millisecond)
cl := mciasv1.NewAuthServiceClient(e.conn)
ctx := authCtx(tok)
@@ -378,6 +390,28 @@ func TestRenewToken(t *testing.T) {
}
}
// TestRenewTokenTooEarly verifies that a token cannot be renewed before 50%
// of its lifetime has elapsed (SEC-03).
func TestRenewTokenTooEarly(t *testing.T) {
e := newTestEnv(t)
acct := e.createHumanAccount(t, "renewearlyuser")
tok := e.issueUserToken(t, acct)
cl := mciasv1.NewAuthServiceClient(e.conn)
ctx := authCtx(tok)
_, err := cl.RenewToken(ctx, &mciasv1.RenewTokenRequest{})
if err == nil {
t.Fatal("RenewToken: expected error for early renewal, got nil")
}
st, ok := status.FromError(err)
if !ok || st.Code() != codes.InvalidArgument {
t.Fatalf("RenewToken: expected InvalidArgument, got %v", err)
}
if !strings.Contains(st.Message(), "not yet eligible for renewal") {
t.Errorf("RenewToken: expected eligibility message, got: %s", st.Message())
}
}
// ---- TokenService tests ----
// TestValidateToken verifies the public ValidateToken RPC returns valid=true for