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:
@@ -2,7 +2,6 @@ package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -11,207 +10,6 @@ import (
|
||||
"github.com/treytartt/honeydue-api/internal/testutil"
|
||||
)
|
||||
|
||||
// === Password Reset Code Lifecycle ===
|
||||
|
||||
func TestUserRepository_PasswordResetCode_Lifecycle(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_abc123", "reset_token_xyz", expiresAt)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, code.ID)
|
||||
assert.Equal(t, "hash_abc123", code.CodeHash)
|
||||
assert.Equal(t, "reset_token_xyz", code.ResetToken)
|
||||
assert.False(t, code.Used)
|
||||
assert.Equal(t, 0, code.Attempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_CreatePasswordResetCode_InvalidatesExisting(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
|
||||
code1, err := repo.CreatePasswordResetCode(user.ID, "hash1", "token1", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.CreatePasswordResetCode(user.ID, "hash2", "token2", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First code should be marked as used
|
||||
var c models.PasswordResetCode
|
||||
db.First(&c, code1.ID)
|
||||
assert.True(t, c.Used)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_abc", "token_abc", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, foundUser, err := repo.FindPasswordResetCodeByEmail("test@example.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, foundUser.ID)
|
||||
assert.Equal(t, "hash_abc", found.CodeHash)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail_UserNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, _, err := repo.FindPasswordResetCodeByEmail("nonexistent@example.com")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail_NoCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
_, _, err := repo.FindPasswordResetCodeByEmail("test@example.com")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_xyz", "token_xyz", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindPasswordResetCodeByToken("token_xyz")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hash_xyz", found.CodeHash)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindPasswordResetCodeByToken("nonexistent_token")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_Expired(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Already expired
|
||||
expiresAt := time.Now().UTC().Add(-1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_exp", "token_exp", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_exp")
|
||||
assert.ErrorIs(t, err, ErrCodeExpired)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_Used(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_used", "token_used", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mark as used
|
||||
err = repo.MarkPasswordResetCodeUsed(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_used")
|
||||
assert.ErrorIs(t, err, ErrCodeUsed)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_TooManyAttempts(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_attempts", "token_attempts", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Max out attempts
|
||||
for i := 0; i < 5; i++ {
|
||||
err = repo.IncrementResetCodeAttempts(code.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_attempts")
|
||||
assert.ErrorIs(t, err, ErrTooManyAttempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_IncrementResetCodeAttempts(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_inc", "token_inc", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.IncrementResetCodeAttempts(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updated models.PasswordResetCode
|
||||
db.First(&updated, code.ID)
|
||||
assert.Equal(t, 1, updated.Attempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_MarkPasswordResetCodeUsed(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_mark", "token_mark", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.MarkPasswordResetCodeUsed(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updated models.PasswordResetCode
|
||||
db.First(&updated, code.ID)
|
||||
assert.True(t, updated.Used)
|
||||
}
|
||||
|
||||
func TestUserRepository_CountRecentPasswordResetRequests(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash1", "token1", expiresAt)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.CreatePasswordResetCode(user.ID, "hash2", "token2", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := repo.CountRecentPasswordResetRequests(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), count)
|
||||
}
|
||||
|
||||
// === FindUsersInSharedResidences ===
|
||||
|
||||
func TestUserRepository_FindUsersInSharedResidences(t *testing.T) {
|
||||
@@ -301,33 +99,6 @@ func TestUserRepository_FindProfilesInSharedResidences(t *testing.T) {
|
||||
assert.Len(t, profiles, 2)
|
||||
}
|
||||
|
||||
// === ConfirmationCode Expired ===
|
||||
|
||||
func TestUserRepository_FindConfirmationCode_Expired(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create already-expired code
|
||||
expiresAt := time.Now().UTC().Add(-1 * time.Hour)
|
||||
_, err := repo.CreateConfirmationCode(user.ID, "999999", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindConfirmationCode(user.ID, "999999")
|
||||
assert.ErrorIs(t, err, ErrCodeExpired)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindConfirmationCode_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
_, err := repo.FindConfirmationCode(user.ID, "000000")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
// === Transaction Rollback ===
|
||||
|
||||
func TestUserRepository_Transaction_Rollback(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user