603 lines
19 KiB
Go
603 lines
19 KiB
Go
package e2e
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"goyco/internal/config"
|
|
"goyco/internal/testutils"
|
|
)
|
|
|
|
func TestE2E_SessionFixation(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("session_fixation", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "sessionfix", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
oldToken := authClient.Token
|
|
oldRefreshToken := authClient.RefreshToken
|
|
|
|
authClient.UpdatePassword(t, "Password123!", "NewPassword456!")
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, oldToken)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected old token to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
oldClient := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: oldToken,
|
|
RefreshToken: oldRefreshToken,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldClient.RefreshAccessToken(t)
|
|
if statusCode == http.StatusOK {
|
|
t.Errorf("Expected old refresh token to be invalidated after password change, but refresh succeeded")
|
|
}
|
|
|
|
newAuthClient := ctx.loginUser(t, createdUser.Username, "NewPassword456!")
|
|
if newAuthClient.Token == "" {
|
|
t.Errorf("Expected to be able to login with new password")
|
|
}
|
|
|
|
profile := newAuthClient.GetProfile(t)
|
|
if profile.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected to access profile with new token, got username '%s'", profile.Data.Username)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenInvalidationOnPasswordChange(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_invalidation_on_password_change", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "tokeninv", "Password123!")
|
|
|
|
authClient1 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
token1 := authClient1.Token
|
|
refreshToken1 := authClient1.RefreshToken
|
|
|
|
authClient2 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
token2 := authClient2.Token
|
|
refreshToken2 := authClient2.RefreshToken
|
|
|
|
authClient3 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
token3 := authClient3.Token
|
|
refreshToken3 := authClient3.RefreshToken
|
|
|
|
profile1 := authClient1.GetProfile(t)
|
|
if profile1.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected token1 to work before password change")
|
|
}
|
|
|
|
authClient1.UpdatePassword(t, "Password123!", "NewPassword789!")
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, token1)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected token1 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
statusCode = ctx.makeRequestWithToken(t, token2)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected token2 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
statusCode = ctx.makeRequestWithToken(t, token3)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected token3 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
oldClient1 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: token1,
|
|
RefreshToken: refreshToken1,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldClient1.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refreshToken1 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
oldClient2 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: token2,
|
|
RefreshToken: refreshToken2,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldClient2.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refreshToken2 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
oldClient3 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: token3,
|
|
RefreshToken: refreshToken3,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldClient3.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refreshToken3 to be invalidated after password change, got status %d", statusCode)
|
|
}
|
|
|
|
newAuthClient := ctx.loginUser(t, createdUser.Username, "NewPassword789!")
|
|
if newAuthClient.Token == "" {
|
|
t.Errorf("Expected to be able to login with new password")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenInvalidationOnEmailChange(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_invalidation_on_email_change", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "emailchange", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
oldToken := authClient.Token
|
|
|
|
ctx.server.EmailSender.Reset()
|
|
authClient.UpdateEmail(t, uniqueEmail(t, "newemail"))
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, oldToken)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Email change does not invalidate tokens (acceptable behavior)")
|
|
}
|
|
|
|
_, statusCode = authClient.RefreshAccessToken(t)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Email change does not invalidate refresh tokens (acceptable behavior)")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_SessionVersionIncrements(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("session_version_increments", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "sessionver", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
user, err := ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user: %v", err)
|
|
}
|
|
|
|
initialVersion := user.SessionVersion
|
|
if initialVersion == 0 {
|
|
t.Errorf("Expected initial session version to be >= 1, got %d", initialVersion)
|
|
}
|
|
|
|
authClient.UpdatePassword(t, "Password123!", "NewPassword999!")
|
|
|
|
user, err = ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user after password change: %v", err)
|
|
}
|
|
|
|
if user.SessionVersion <= initialVersion {
|
|
t.Errorf("Expected session version to increment after password change, got %d (was %d)", user.SessionVersion, initialVersion)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_OldTokensRejectedAfterSessionVersionChange(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("old_tokens_rejected_after_session_version_change", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "oldtoken", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
user, err := ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user: %v", err)
|
|
}
|
|
|
|
oldSessionVersion := user.SessionVersion
|
|
oldToken := authClient.Token
|
|
|
|
cfg := &config.Config{
|
|
JWT: config.JWTConfig{
|
|
Secret: "test-secret-key-for-testing-purposes-only",
|
|
Expiration: 24,
|
|
RefreshExpiration: 168,
|
|
Issuer: "goyco",
|
|
Audience: "goyco-users",
|
|
},
|
|
}
|
|
|
|
authClient.UpdatePassword(t, "Password123!", "NewPassword888!")
|
|
|
|
user, err = ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user after password change: %v", err)
|
|
}
|
|
|
|
if user.SessionVersion == oldSessionVersion {
|
|
t.Errorf("Expected session version to change after password update")
|
|
}
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, oldToken)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected old token to be rejected after session version change, got status %d", statusCode)
|
|
}
|
|
|
|
tokenWithOldVersion := generateTokenWithSessionVersion(t, user, &cfg.JWT, oldSessionVersion)
|
|
statusCode = ctx.makeRequestWithToken(t, tokenWithOldVersion)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected token with old session version to be rejected, got status %d", statusCode)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenRefreshWithOldSessionVersion(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_refresh_with_old_session_version", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "refreshold", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
oldRefreshToken := authClient.RefreshToken
|
|
|
|
authClient.UpdatePassword(t, "Password123!", "NewPassword777!")
|
|
|
|
oldClient := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: authClient.Token,
|
|
RefreshToken: oldRefreshToken,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
|
|
_, statusCode := oldClient.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refresh with old refresh token to fail after password change, got status %d", statusCode)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_MultiDeviceSession(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("multi_device_session", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "multidev", "Password123!")
|
|
|
|
deviceA := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
tokenA := deviceA.Token
|
|
|
|
deviceB := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
tokenB := deviceB.Token
|
|
|
|
profileA := deviceA.GetProfile(t)
|
|
if profileA.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected device A to access profile")
|
|
}
|
|
|
|
profileB := deviceB.GetProfile(t)
|
|
if profileB.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected device B to access profile")
|
|
}
|
|
|
|
deviceA.Logout(t)
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, tokenA)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Logout may not invalidate tokens immediately (acceptable)")
|
|
}
|
|
|
|
profileBAfter := deviceB.GetProfile(t)
|
|
if profileBAfter.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected device B to still work after device A logout")
|
|
}
|
|
|
|
deviceB.RevokeAllTokens(t)
|
|
|
|
_, statusCode = deviceB.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected device B refresh token to be revoked after revoke-all, got status %d", statusCode)
|
|
}
|
|
|
|
statusCode = ctx.makeRequestWithToken(t, tokenB)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Access token may still work after refresh token revocation (acceptable)")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_RevokeAllInvalidatesAllDevices(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("revoke_all_invalidates_all_devices", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "revokeall", "Password123!")
|
|
|
|
device1 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
refreshToken1 := device1.RefreshToken
|
|
|
|
device2 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
refreshToken2 := device2.RefreshToken
|
|
|
|
device3 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
refreshToken3 := device3.RefreshToken
|
|
|
|
device1.RevokeAllTokens(t)
|
|
|
|
oldDevice1 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: device1.Token,
|
|
RefreshToken: refreshToken1,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode := oldDevice1.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected device1 refresh token to be revoked, got status %d", statusCode)
|
|
}
|
|
|
|
oldDevice2 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: device2.Token,
|
|
RefreshToken: refreshToken2,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldDevice2.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected device2 refresh token to be revoked, got status %d", statusCode)
|
|
}
|
|
|
|
oldDevice3 := &AuthenticatedClient{
|
|
AuthenticatedClient: &testutils.AuthenticatedClient{
|
|
Client: ctx.client,
|
|
Token: device3.Token,
|
|
RefreshToken: refreshToken3,
|
|
BaseURL: ctx.baseURL,
|
|
},
|
|
}
|
|
_, statusCode = oldDevice3.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected device3 refresh token to be revoked, got status %d", statusCode)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenTiming(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_timing", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "timing", "Password123!")
|
|
|
|
cfg := &config.Config{
|
|
JWT: config.JWTConfig{
|
|
Secret: "test-secret-key-for-testing-purposes-only",
|
|
Expiration: 24,
|
|
RefreshExpiration: 168,
|
|
Issuer: "goyco",
|
|
Audience: "goyco-users",
|
|
},
|
|
}
|
|
|
|
user, err := ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user: %v", err)
|
|
}
|
|
|
|
t.Run("token_just_before_expiry", func(t *testing.T) {
|
|
token := generateTokenWithExpiration(t, user, &cfg.JWT, 1*time.Minute)
|
|
statusCode := ctx.makeRequestWithToken(t, token)
|
|
if statusCode != http.StatusOK {
|
|
t.Errorf("Expected token just before expiry to work, got status %d", statusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("token_just_after_expiry", func(t *testing.T) {
|
|
token := generateTokenWithExpiration(t, user, &cfg.JWT, -1*time.Minute)
|
|
statusCode := ctx.makeRequestWithToken(t, token)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected expired token to be rejected, got status %d", statusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("token_expiration_edge_case", func(t *testing.T) {
|
|
token := generateTokenWithExpiration(t, user, &cfg.JWT, 0)
|
|
statusCode := ctx.makeRequestWithToken(t, token)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Token with zero expiration may be accepted (clock skew tolerance)")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenReplayAttack(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_replay_attack", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "replay", "Password123!")
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
token := authClient.Token
|
|
|
|
t.Run("same_token_multiple_times", func(t *testing.T) {
|
|
for i := 0; i < 5; i++ {
|
|
statusCode := ctx.makeRequestWithToken(t, token)
|
|
if statusCode != http.StatusOK {
|
|
t.Errorf("Expected token to work multiple times (replay %d), got status %d", i+1, statusCode)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("token_reuse_after_revocation", func(t *testing.T) {
|
|
authClient.RevokeAllTokens(t)
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, token)
|
|
if statusCode == http.StatusOK {
|
|
t.Log("Access token may still work after refresh token revocation (acceptable)")
|
|
}
|
|
|
|
_, statusCode = authClient.RefreshAccessToken(t)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refresh token to be rejected after revocation, got status %d", statusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("token_reuse_after_user_deletion", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "deleteuser", "Password123!")
|
|
deleteClient := ctx.loginUser(t, testUser.Username, testUser.Password)
|
|
deleteToken := deleteClient.Token
|
|
|
|
ctx.server.EmailSender.Reset()
|
|
deleteClient.RequestAccountDeletion(t)
|
|
deletionToken := ctx.server.EmailSender.DeletionToken()
|
|
if deletionToken == "" {
|
|
t.Fatalf("Expected deletion token")
|
|
}
|
|
|
|
deleteClient.ConfirmAccountDeletion(t, deletionToken, false)
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, deleteToken)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected token to be rejected after user deletion, got status %d", statusCode)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestE2E_TokenScope(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("token_scope", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "scope", "Password123!")
|
|
|
|
cfg := &config.Config{
|
|
JWT: config.JWTConfig{
|
|
Secret: "test-secret-key-for-testing-purposes-only",
|
|
Expiration: 24,
|
|
RefreshExpiration: 168,
|
|
Issuer: "goyco",
|
|
Audience: "goyco-users",
|
|
},
|
|
}
|
|
|
|
user, err := ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user: %v", err)
|
|
}
|
|
|
|
t.Run("access_token_cannot_be_used_as_refresh", func(t *testing.T) {
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
accessToken := authClient.Token
|
|
|
|
refreshData := map[string]string{
|
|
"refresh_token": accessToken,
|
|
}
|
|
|
|
body, err := json.Marshal(refreshData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal refresh data: %v", err)
|
|
}
|
|
|
|
request, err := http.NewRequest("POST", ctx.baseURL+"/api/auth/refresh", bytes.NewReader(body))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create refresh request: %v", err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(request)
|
|
|
|
resp, err := ctx.client.Do(request)
|
|
if err != nil {
|
|
t.Fatalf("Failed to make refresh request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
t.Errorf("Expected access token to be rejected as refresh token, got status 200")
|
|
}
|
|
})
|
|
|
|
t.Run("refresh_token_cannot_access_protected_endpoints", func(t *testing.T) {
|
|
authClient := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
refreshTokenString := authClient.RefreshToken
|
|
|
|
statusCode := ctx.makeRequestWithToken(t, refreshTokenString)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected refresh token string to be rejected for protected endpoints, got status %d", statusCode)
|
|
}
|
|
|
|
invalidTypeToken := generateTokenWithType(t, user, &cfg.JWT, "invalid-type")
|
|
statusCode = ctx.makeRequestWithToken(t, invalidTypeToken)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected invalid token type to be rejected, got status %d", statusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("token_type_validation", func(t *testing.T) {
|
|
emptyTypeToken := generateTokenWithType(t, user, &cfg.JWT, "")
|
|
statusCode := ctx.makeRequestWithToken(t, emptyTypeToken)
|
|
if statusCode != http.StatusUnauthorized {
|
|
t.Errorf("Expected empty token type to be rejected, got status %d", statusCode)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestE2E_ConcurrentLoginPrevention(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("concurrent_login_prevention", func(t *testing.T) {
|
|
createdUser := ctx.createUserWithCleanup(t, "concurrent", "Password123!")
|
|
|
|
user, err := ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user: %v", err)
|
|
}
|
|
|
|
initialVersion := user.SessionVersion
|
|
|
|
login1 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
login2 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
login3 := ctx.loginUser(t, createdUser.Username, createdUser.Password)
|
|
|
|
user, err = ctx.server.UserRepo.GetByID(createdUser.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get user after logins: %v", err)
|
|
}
|
|
|
|
if user.SessionVersion != initialVersion {
|
|
t.Log("Session version may increment on login (acceptable behavior)")
|
|
}
|
|
|
|
profile1 := login1.GetProfile(t)
|
|
if profile1.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected login1 to work")
|
|
}
|
|
|
|
profile2 := login2.GetProfile(t)
|
|
if profile2.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected login2 to work")
|
|
}
|
|
|
|
profile3 := login3.GetProfile(t)
|
|
if profile3.Data.Username != createdUser.Username {
|
|
t.Errorf("Expected login3 to work")
|
|
}
|
|
|
|
if login1.Token == login2.Token || login1.Token == login3.Token || login2.Token == login3.Token {
|
|
t.Errorf("Expected concurrent logins to generate different tokens")
|
|
}
|
|
})
|
|
}
|