package authtoken import ( "context" "testing" "time" "github.com/google/uuid" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nixcn-cms/data" "nixcn-cms/testutil" ) func makeToken() Token { return Token{Application: "test-app"} } // seedClient persists a client row whose encrypted secret decrypts to the // plaintext given by testutil.TestClientSecret. func seedTestClient(t *testing.T) { t.Helper() testutil.SeedClient(t) } func TestGenerateRefreshToken(t *testing.T) { tok := makeToken() r1, err := tok.GenerateRefreshToken() require.NoError(t, err) assert.NotEmpty(t, r1) r2, err := tok.GenerateRefreshToken() require.NoError(t, err) assert.NotEqual(t, r1, r2, "refresh tokens must be unique") } func TestIssueTokens(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() access, refresh, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) assert.NotEmpty(t, access) assert.NotEmpty(t, refresh) } func TestRefreshAccessToken(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() _, refresh, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) newAccess, err := tok.RefreshAccessToken(ctx, refresh) require.NoError(t, err) assert.NotEmpty(t, newAccess) } func TestRefreshAccessTokenInvalid(t *testing.T) { testutil.Setup(t) ctx := context.Background() tok := makeToken() _, err := tok.RefreshAccessToken(ctx, "invalid-refresh-token") require.Error(t, err) } func TestRenewRefreshToken(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() _, old, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) newRefresh, err := tok.RenewRefreshToken(ctx, old) require.NoError(t, err) assert.NotEmpty(t, newRefresh) assert.NotEqual(t, old, newRefresh) // Old token should no longer be valid _, err = tok.RefreshAccessToken(ctx, old) require.Error(t, err) } func TestRevokeRefreshToken(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() _, refresh, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) require.NoError(t, tok.RevokeRefreshToken(ctx, refresh)) _, err = tok.RefreshAccessToken(ctx, refresh) require.Error(t, err) } func TestRevokeNonExistentTokenNoError(t *testing.T) { testutil.Setup(t) ctx := context.Background() tok := makeToken() // should not fail even if token does not exist assert.NoError(t, tok.RevokeRefreshToken(ctx, "does-not-exist")) } func TestHeaderVerifyValid(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() access, _, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) uid, err := tok.HeaderVerify(ctx, "Bearer "+access) require.NoError(t, err) assert.Equal(t, userId.String(), uid) } func TestHeaderVerifyEmpty(t *testing.T) { testutil.Setup(t) ctx := context.Background() tok := makeToken() uid, err := tok.HeaderVerify(ctx, "") require.Error(t, err) assert.Empty(t, uid) } func TestHeaderVerifyMalformed(t *testing.T) { testutil.Setup(t) ctx := context.Background() tok := makeToken() _, err := tok.HeaderVerify(ctx, "NotBearer token") require.Error(t, err) } func TestHeaderVerifyExpiredToken(t *testing.T) { testutil.Setup(t) seedTestClient(t) // Use a very short access TTL viper.Set("ttl.access_ttl", -1*time.Second) ctx := context.Background() userId := uuid.New() tok := makeToken() access, _, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) _, err = tok.HeaderVerify(ctx, "Bearer "+access) require.Error(t, err) } func TestNewClaims(t *testing.T) { tok := makeToken() userId := uuid.New() claims := tok.NewClaims(testutil.TestClientID, userId) assert.Equal(t, testutil.TestClientID, claims.ClientId) assert.Equal(t, userId, claims.UserID) assert.Equal(t, "test-app", claims.Issuer) } // Verify that IssueTokens registers the refresh token in the user and // client index sets in Redis. func TestIssueTokensRedisIndex(t *testing.T) { testutil.Setup(t) seedTestClient(t) ctx := context.Background() userId := uuid.New() tok := makeToken() _, refresh, err := tok.IssueTokens(ctx, testutil.TestClientID, userId) require.NoError(t, err) userSetKey := "user:" + userId.String() + ":refresh_tokens" isMember, err := data.Redis.SIsMember(ctx, userSetKey, refresh).Result() require.NoError(t, err) assert.True(t, isMember) }