feat(auth): replace hand-rolled auth with Ory Kratos — phase 2 backend
Delegates all credential management (login, register, password reset, email verification, social sign-in) to Ory Kratos. The Go API now acts as a resource server: the new KratosAuth middleware validates sessions against the Kratos whoami endpoint, writes the local User mirror into Echo context, and all existing domain handlers continue working unchanged. Hand-rolled token auth, AuthToken model, apple_auth/ google_auth services, and the auth refresh flow are removed. Tests are updated to use the fake-token middleware pattern so existing integration assertions require no rewrite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -19,195 +18,18 @@ func setupAuthService(t *testing.T) (*AuthService, *repositories.UserRepository)
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
notifRepo := repositories.NewNotificationRepository(db)
|
||||
cfg := &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
DebugFixedCodes: true,
|
||||
},
|
||||
Security: config.SecurityConfig{
|
||||
SecretKey: "test-secret",
|
||||
ConfirmationExpiry: 24 * time.Hour,
|
||||
PasswordResetExpiry: 15 * time.Minute,
|
||||
MaxPasswordResetRate: 3,
|
||||
TokenExpiryDays: 90,
|
||||
TokenRefreshDays: 60,
|
||||
},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
service.SetNotificationRepository(notifRepo)
|
||||
return service, userRepo
|
||||
}
|
||||
|
||||
// === Login ===
|
||||
|
||||
func TestAuthService_Login(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
resp, err := service.Login(context.Background(), req, "")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp.Token)
|
||||
assert.Equal(t, "testuser", resp.User.Username)
|
||||
}
|
||||
|
||||
func TestAuthService_Login_ByEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
resp, err := service.Login(context.Background(), req, "")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp.Token)
|
||||
}
|
||||
|
||||
func TestAuthService_Login_InvalidCredentials(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "WrongPassword1",
|
||||
}
|
||||
|
||||
_, err := service.Login(context.Background(), req, "")
|
||||
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
|
||||
}
|
||||
|
||||
func TestAuthService_Login_UserNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Username: "nonexistent",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
_, err := service.Login(context.Background(), req, "")
|
||||
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
|
||||
}
|
||||
|
||||
func TestAuthService_Login_InactiveUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "inactive", "inactive@test.com", "Password123")
|
||||
// Deactivate
|
||||
user.IsActive = false
|
||||
db.Save(user)
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Username: "inactive",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
_, err := service.Login(context.Background(), req, "")
|
||||
// Audit L1: inactive accounts return the same generic error as bad
|
||||
// credentials so login does not disclose which accounts exist.
|
||||
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
|
||||
}
|
||||
|
||||
// === Register ===
|
||||
|
||||
func TestAuthService_Register(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
resp, code, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp.Token)
|
||||
assert.Equal(t, "newuser", resp.User.Username)
|
||||
assert.Equal(t, "123456", code) // DebugFixedCodes=true
|
||||
}
|
||||
|
||||
func TestAuthService_Register_DuplicateUsername(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Server: config.ServerConfig{DebugFixedCodes: true},
|
||||
Security: config.SecurityConfig{SecretKey: "test", ConfirmationExpiry: 24 * time.Hour},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "taken", "taken@test.com", "Password123")
|
||||
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "taken",
|
||||
Email: "different@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
testutil.AssertAppError(t, err, http.StatusConflict, "error.username_taken")
|
||||
}
|
||||
|
||||
func TestAuthService_Register_DuplicateEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Server: config.ServerConfig{DebugFixedCodes: true},
|
||||
Security: config.SecurityConfig{SecretKey: "test", ConfirmationExpiry: 24 * time.Hour},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "existing", "taken@test.com", "Password123")
|
||||
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "taken@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
testutil.AssertAppError(t, err, http.StatusConflict, "error.email_taken")
|
||||
}
|
||||
|
||||
// === GetCurrentUser ===
|
||||
|
||||
func TestAuthService_GetCurrentUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
@@ -218,7 +40,7 @@ func TestAuthService_GetCurrentUser(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "testuser", resp.Username)
|
||||
assert.Equal(t, "test@test.com", resp.Email)
|
||||
assert.Equal(t, "email", resp.AuthProvider) // Default for no social auth
|
||||
assert.Equal(t, "kratos", resp.AuthProvider) // All users are Kratos-managed
|
||||
}
|
||||
|
||||
// === UpdateProfile ===
|
||||
@@ -226,9 +48,7 @@ func TestAuthService_GetCurrentUser(t *testing.T) {
|
||||
func TestAuthService_UpdateProfile(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
@@ -250,9 +70,7 @@ func TestAuthService_UpdateProfile(t *testing.T) {
|
||||
func TestAuthService_UpdateProfile_DuplicateEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "user1", "user1@test.com", "Password123")
|
||||
@@ -271,9 +89,7 @@ func TestAuthService_UpdateProfile_DuplicateEmail(t *testing.T) {
|
||||
func TestAuthService_UpdateProfile_SameEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
@@ -290,443 +106,10 @@ func TestAuthService_UpdateProfile_SameEmail(t *testing.T) {
|
||||
assert.Equal(t, "test@test.com", resp.Email)
|
||||
}
|
||||
|
||||
// === VerifyEmail ===
|
||||
|
||||
func TestAuthService_VerifyEmail(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register a user (creates confirmation code)
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get the user ID
|
||||
user, err := service.userRepo.FindByEmail("new@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify with the debug code
|
||||
err = service.VerifyEmail(context.Background(), user.ID, "123456")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify again — should get already verified error
|
||||
err = service.VerifyEmail(context.Background(), user.ID, "123456")
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.email_already_verified")
|
||||
}
|
||||
|
||||
func TestAuthService_VerifyEmail_InvalidCode(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("new@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wrong code — with DebugFixedCodes enabled, "123456" bypasses normal lookup,
|
||||
// but a wrong code should use the normal path
|
||||
err = service.VerifyEmail(context.Background(), user.ID, "000000")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === ResendVerificationCode ===
|
||||
|
||||
func TestAuthService_ResendVerificationCode(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("new@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
code, err := service.ResendVerificationCode(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "123456", code) // DebugFixedCodes
|
||||
}
|
||||
|
||||
func TestAuthService_ResendVerificationCode_AlreadyVerified(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register and verify
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("new@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.VerifyEmail(context.Background(), user.ID, "123456")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.ResendVerificationCode(context.Background(), user.ID)
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.email_already_verified")
|
||||
}
|
||||
|
||||
// === ForgotPassword ===
|
||||
|
||||
func TestAuthService_ForgotPassword(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register a user first
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
code, user, err := service.ForgotPassword(context.Background(), "test@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "123456", code) // DebugFixedCodes
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, "test@test.com", user.Email)
|
||||
}
|
||||
|
||||
func TestAuthService_ForgotPassword_NonexistentEmail(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Should not reveal that email doesn't exist
|
||||
code, user, err := service.ForgotPassword(context.Background(), "nonexistent@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, code)
|
||||
assert.Nil(t, user)
|
||||
}
|
||||
|
||||
// === ResetPassword ===
|
||||
|
||||
func TestAuthService_ResetPassword(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Forgot password
|
||||
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify reset code to get the token
|
||||
resetToken, err := service.VerifyResetCode(context.Background(), "test@test.com", "123456")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resetToken)
|
||||
|
||||
// Reset password
|
||||
err = service.ResetPassword(context.Background(), resetToken, "NewPassword123")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Login with new password
|
||||
loginReq := &requests.LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "NewPassword123",
|
||||
}
|
||||
loginResp, err := service.Login(context.Background(), loginReq, "")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, loginResp.Token)
|
||||
}
|
||||
|
||||
func TestAuthService_ResetPassword_InvalidToken(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
err := service.ResetPassword(context.Background(), "invalid-token", "NewPassword123")
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.invalid_reset_token")
|
||||
}
|
||||
|
||||
// === Logout ===
|
||||
|
||||
func TestAuthService_Logout(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
|
||||
// Login first
|
||||
loginReq := &requests.LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "Password123",
|
||||
}
|
||||
loginResp, err := service.Login(context.Background(), loginReq, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Logout
|
||||
err = service.Logout(context.Background(), loginResp.Token)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Token should be deleted — refreshing should fail
|
||||
_, err = service.RefreshToken(context.Background(), loginResp.Token, user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === DeleteAccount ===
|
||||
|
||||
func TestAuthService_DeleteAccount_EmailAuth(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
// Register
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
password := "Password123"
|
||||
_, err = service.DeleteAccount(context.Background(), user.ID, &password, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_WrongPassword(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
wrongPassword := "WrongPassword1"
|
||||
_, err = service.DeleteAccount(context.Background(), user.ID, &wrongPassword, nil)
|
||||
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_NoPassword(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.DeleteAccount(context.Background(), user.ID, nil, nil)
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.password_required")
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_UserNotFound(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
password := "Password123"
|
||||
_, err := service.DeleteAccount(context.Background(), 99999, &password, nil)
|
||||
testutil.AssertAppError(t, err, http.StatusNotFound, "error.user_not_found")
|
||||
}
|
||||
|
||||
// === Helper functions ===
|
||||
|
||||
func TestGenerateSixDigitCode(t *testing.T) {
|
||||
code := generateSixDigitCode()
|
||||
assert.Len(t, code, 6)
|
||||
// Should be numeric
|
||||
for _, c := range code {
|
||||
assert.True(t, c >= '0' && c <= '9', "code should contain only digits")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateResetToken(t *testing.T) {
|
||||
token := generateResetToken()
|
||||
assert.NotEmpty(t, token)
|
||||
assert.Len(t, token, 64) // 32 bytes = 64 hex chars
|
||||
}
|
||||
|
||||
func TestGetStringOrEmpty(t *testing.T) {
|
||||
s := "hello"
|
||||
assert.Equal(t, "hello", getStringOrEmpty(&s))
|
||||
assert.Equal(t, "", getStringOrEmpty(nil))
|
||||
}
|
||||
|
||||
func TestIsPrivateRelayEmail(t *testing.T) {
|
||||
assert.True(t, isPrivateRelayEmail("abc@privaterelay.appleid.com"))
|
||||
assert.True(t, isPrivateRelayEmail("ABC@PRIVATERELAY.APPLEID.COM"))
|
||||
assert.False(t, isPrivateRelayEmail("user@gmail.com"))
|
||||
}
|
||||
|
||||
func TestGetEmailFromRequest(t *testing.T) {
|
||||
email := "req@test.com"
|
||||
assert.Equal(t, "req@test.com", getEmailFromRequest(&email, "claims@test.com"))
|
||||
assert.Equal(t, "claims@test.com", getEmailFromRequest(nil, "claims@test.com"))
|
||||
empty := ""
|
||||
assert.Equal(t, "claims@test.com", getEmailFromRequest(&empty, "claims@test.com"))
|
||||
}
|
||||
|
||||
// === getEmailOrDefault ===
|
||||
|
||||
func TestGetEmailOrDefault(t *testing.T) {
|
||||
// Non-empty email returns itself
|
||||
assert.Equal(t, "user@test.com", getEmailOrDefault("user@test.com"))
|
||||
|
||||
// Empty email returns a generated placeholder
|
||||
result := getEmailOrDefault("")
|
||||
assert.Contains(t, result, "@privaterelay.appleid.com")
|
||||
assert.Contains(t, result, "apple_")
|
||||
}
|
||||
|
||||
// === generateUniqueUsername ===
|
||||
|
||||
func TestGenerateUniqueUsername(t *testing.T) {
|
||||
// Normal email generates username from email prefix
|
||||
username := generateUniqueUsername("john@test.com", nil)
|
||||
assert.Contains(t, username, "john_")
|
||||
|
||||
// Private relay email falls back to first name
|
||||
firstName := "Jane"
|
||||
username = generateUniqueUsername("abc@privaterelay.appleid.com", &firstName)
|
||||
assert.Contains(t, username, "jane_")
|
||||
|
||||
// Private relay email and no first name — fallback
|
||||
username = generateUniqueUsername("abc@privaterelay.appleid.com", nil)
|
||||
assert.Contains(t, username, "user_")
|
||||
|
||||
// Empty email with first name
|
||||
firstName2 := "Bob"
|
||||
username = generateUniqueUsername("", &firstName2)
|
||||
assert.Contains(t, username, "bob_")
|
||||
|
||||
// Empty email and no first name
|
||||
username = generateUniqueUsername("", nil)
|
||||
assert.Contains(t, username, "user_")
|
||||
}
|
||||
|
||||
// === generateGoogleUsername ===
|
||||
|
||||
func TestGenerateGoogleUsername(t *testing.T) {
|
||||
// Normal email
|
||||
username := generateGoogleUsername("john@gmail.com", "John")
|
||||
assert.Contains(t, username, "john_")
|
||||
|
||||
// Empty email falls back to first name
|
||||
username = generateGoogleUsername("", "Alice")
|
||||
assert.Contains(t, username, "alice_")
|
||||
|
||||
// Empty email and empty first name — fallback
|
||||
username = generateGoogleUsername("", "")
|
||||
assert.Contains(t, username, "google_")
|
||||
}
|
||||
|
||||
// === Login with empty password ===
|
||||
|
||||
func TestAuthService_Login_EmptyPassword(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
|
||||
req := &requests.LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "",
|
||||
}
|
||||
|
||||
_, err := service.Login(context.Background(), req, "")
|
||||
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
|
||||
}
|
||||
|
||||
// === ForgotPassword rate limiting ===
|
||||
|
||||
func TestAuthService_ForgotPassword_RateLimit(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make max allowed reset requests (3 based on setup)
|
||||
for i := 0; i < 3; i++ {
|
||||
_, _, err := service.ForgotPassword(context.Background(), "test@test.com")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The 4th should be rate limited
|
||||
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === VerifyResetCode with wrong code ===
|
||||
|
||||
func TestAuthService_VerifyResetCode_WrongCode(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wrong code but with debug mode, "123456" works, "000000" should fail
|
||||
_, err = service.VerifyResetCode(context.Background(), "test@test.com", "000000")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === VerifyResetCode with nonexistent email ===
|
||||
|
||||
func TestAuthService_VerifyResetCode_NonexistentEmail(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
_, err := service.VerifyResetCode(context.Background(), "nonexistent@test.com", "123456")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === UpdateProfile — change email to new email ===
|
||||
|
||||
func TestAuthService_UpdateProfile_ChangeEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@test.com", "Password123")
|
||||
@@ -742,25 +125,44 @@ func TestAuthService_UpdateProfile_ChangeEmail(t *testing.T) {
|
||||
assert.Equal(t, "newemail@test.com", resp.Email)
|
||||
}
|
||||
|
||||
// === DeleteAccount — empty password string ===
|
||||
// === DeleteAccount ===
|
||||
|
||||
func TestAuthService_DeleteAccount_EmptyPassword(t *testing.T) {
|
||||
func TestAuthService_DeleteAccount_WithConfirmation(t *testing.T) {
|
||||
service, userRepo := setupAuthService(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, (*userRepo).DB(), "testuser", "test@test.com", "")
|
||||
_ = user
|
||||
|
||||
confirmation := "DELETE"
|
||||
_, err := service.DeleteAccount(context.Background(), user.ID, nil, &confirmation)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_WrongConfirmation(t *testing.T) {
|
||||
service, userRepo := setupAuthService(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, (*userRepo).DB(), "testuser", "test@test.com", "")
|
||||
|
||||
wrongConf := "delete"
|
||||
_, err := service.DeleteAccount(context.Background(), user.ID, nil, &wrongConf)
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.confirmation_required")
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_NoConfirmation(t *testing.T) {
|
||||
service, userRepo := setupAuthService(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, (*userRepo).DB(), "testuser", "test@test.com", "")
|
||||
|
||||
_, err := service.DeleteAccount(context.Background(), user.ID, nil, nil)
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.confirmation_required")
|
||||
}
|
||||
|
||||
func TestAuthService_DeleteAccount_UserNotFound(t *testing.T) {
|
||||
service, _ := setupAuthService(t)
|
||||
|
||||
registerReq := &requests.RegisterRequest{
|
||||
Username: "testuser",
|
||||
Email: "test@test.com",
|
||||
Password: "Password123",
|
||||
}
|
||||
_, _, err := service.Register(context.Background(), registerReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := service.userRepo.FindByEmail("test@test.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
emptyPw := ""
|
||||
_, err = service.DeleteAccount(context.Background(), user.ID, &emptyPw, nil)
|
||||
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.password_required")
|
||||
confirmation := "DELETE"
|
||||
_, err := service.DeleteAccount(context.Background(), 99999, nil, &confirmation)
|
||||
testutil.AssertAppError(t, err, http.StatusNotFound, "error.user_not_found")
|
||||
}
|
||||
|
||||
// === SetNotificationRepository ===
|
||||
@@ -769,35 +171,10 @@ func TestAuthService_SetNotificationRepository(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
notifRepo := repositories.NewNotificationRepository(db)
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{SecretKey: "test-secret"},
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
service := NewAuthService(userRepo, cfg)
|
||||
assert.Nil(t, service.notificationRepo)
|
||||
|
||||
service.SetNotificationRepository(notifRepo)
|
||||
assert.NotNil(t, service.notificationRepo)
|
||||
}
|
||||
|
||||
// === Register creates profile and notification preferences ===
|
||||
|
||||
func TestAuthService_Register_CreatesProfile(t *testing.T) {
|
||||
service, userRepo := setupAuthService(t)
|
||||
|
||||
req := &requests.RegisterRequest{
|
||||
Username: "profileuser",
|
||||
Email: "profile@test.com",
|
||||
Password: "Password123",
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
}
|
||||
|
||||
resp, _, err := service.Register(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "profileuser", resp.User.Username)
|
||||
|
||||
// Profile should exist
|
||||
profile, err := userRepo.GetOrCreateProfile(resp.User.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, profile)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user