|
|
|
|
@@ -21,10 +21,9 @@ import (
|
|
|
|
|
"github.com/testcontainers/testcontainers-go"
|
|
|
|
|
"github.com/testcontainers/testcontainers-go/modules/mongodb"
|
|
|
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
|
|
|
"go.mongodb.org/mongo-driver/v2/mongo"
|
|
|
|
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func setupTestDB(t *testing.T) (*refreshtokensdb.RefreshTokenDB, func()) {
|
|
|
|
|
@@ -71,7 +70,7 @@ func setupTestDBWithMongo(t *testing.T) (*refreshtokensdb.RefreshTokenDB, *mongo
|
|
|
|
|
return db, database, cleanup
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createTestRefreshToken(accountRef primitive.ObjectID, clientID, deviceID, token string) *model.RefreshToken {
|
|
|
|
|
func createTestRefreshToken(accountRef bson.ObjectID, clientID, deviceID, token string) *model.RefreshToken {
|
|
|
|
|
return &model.RefreshToken{
|
|
|
|
|
ClientRefreshToken: model.ClientRefreshToken{
|
|
|
|
|
SessionIdentifier: model.SessionIdentifier{
|
|
|
|
|
@@ -99,7 +98,7 @@ func TestRefreshTokenDB_AuthenticationFlow(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
t.Run("Complete_User_Authentication_Flow", func(t *testing.T) {
|
|
|
|
|
// Setup: Create user and client
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-desktop-chrome"
|
|
|
|
|
token := "refresh_token_12345"
|
|
|
|
|
@@ -141,7 +140,7 @@ func TestRefreshTokenDB_AuthenticationFlow(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
t.Run("Manual_Token_Revocation_Workaround", func(t *testing.T) {
|
|
|
|
|
// Test manual revocation by directly updating the token
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-desktop-chrome"
|
|
|
|
|
token := "manual_revoke_token_123"
|
|
|
|
|
@@ -178,7 +177,7 @@ func TestRefreshTokenDB_MultiDeviceManagement(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("User_With_Multiple_Devices", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "mobile-app"
|
|
|
|
|
|
|
|
|
|
// User logs in from phone
|
|
|
|
|
@@ -241,7 +240,7 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Token_Rotation_On_Use", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-browser"
|
|
|
|
|
initialToken := "initial_token_123"
|
|
|
|
|
@@ -298,7 +297,7 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("User_Login_From_Same_Device_Twice", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-laptop"
|
|
|
|
|
|
|
|
|
|
@@ -340,7 +339,7 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("Create_After_GlobalRevocation_AllowsNewActive", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-laptop"
|
|
|
|
|
|
|
|
|
|
@@ -407,7 +406,7 @@ func TestRefreshTokenDB_ClientManagement(t *testing.T) {
|
|
|
|
|
// Note: Client management is handled by a separate client database
|
|
|
|
|
// This test verifies that refresh tokens work with different client IDs
|
|
|
|
|
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
|
|
|
|
|
// Create refresh tokens for different clients
|
|
|
|
|
webToken := createTestRefreshToken(userID, "web-app", "device1", "token1")
|
|
|
|
|
@@ -454,7 +453,7 @@ func TestRefreshTokenDB_SecurityScenarios(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Token_Hijacking_Prevention", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-browser"
|
|
|
|
|
token := "hijacked_token_123"
|
|
|
|
|
@@ -509,7 +508,7 @@ func TestRefreshTokenDB_ExpiredTokenHandling(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Expired_Token_Cleanup", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-device"
|
|
|
|
|
token := "expired_token_123"
|
|
|
|
|
@@ -548,7 +547,7 @@ func TestRefreshTokenDB_ConcurrentAccess(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Concurrent_Token_Usage", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "user-device"
|
|
|
|
|
token := "concurrent_token_123"
|
|
|
|
|
@@ -594,7 +593,7 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Delete_Token_By_ID", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
refreshToken := createTestRefreshToken(userID, "web-app", "device-1", "token_123")
|
|
|
|
|
err := db.Create(ctx, refreshToken)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
@@ -613,7 +612,7 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("Revoke_Non_Existent_Token", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
session := &model.SessionIdentifier{
|
|
|
|
|
ClientID: "non-existent-client",
|
|
|
|
|
DeviceID: "non-existent-device",
|
|
|
|
|
@@ -625,7 +624,7 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("RevokeAll_No_Other_Devices", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
deviceID := "only-device"
|
|
|
|
|
|
|
|
|
|
@@ -659,8 +658,8 @@ func TestRefreshTokenDB_DatabaseIndexes(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("Unique_Token_Constraint", func(t *testing.T) {
|
|
|
|
|
userID1 := primitive.NewObjectID()
|
|
|
|
|
userID2 := primitive.NewObjectID()
|
|
|
|
|
userID1 := bson.NewObjectID()
|
|
|
|
|
userID2 := bson.NewObjectID()
|
|
|
|
|
token := "duplicate_token_123"
|
|
|
|
|
|
|
|
|
|
// Create first token
|
|
|
|
|
@@ -676,7 +675,7 @@ func TestRefreshTokenDB_DatabaseIndexes(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("Query_Performance_By_Revocation_Status", func(t *testing.T) {
|
|
|
|
|
userID := primitive.NewObjectID()
|
|
|
|
|
userID := bson.NewObjectID()
|
|
|
|
|
clientID := "web-app"
|
|
|
|
|
|
|
|
|
|
// Create multiple tokens
|
|
|
|
|
|