feat(auth): replace hand-rolled auth with Ory Kratos — phase 2 backend
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled

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:
Trey t
2026-05-18 17:55:56 -05:00
parent b66151ddd9
commit 81578f6e27
36 changed files with 927 additions and 7002 deletions
+3 -167
View File
@@ -2,50 +2,15 @@ package models
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUser_SetPassword(t *testing.T) {
user := &User{}
err := user.SetPassword("testPassword123")
require.NoError(t, err)
assert.NotEmpty(t, user.Password)
assert.NotEqual(t, "testPassword123", user.Password) // Should be hashed
}
func TestUser_CheckPassword(t *testing.T) {
user := &User{}
err := user.SetPassword("correctpassword")
require.NoError(t, err)
tests := []struct {
name string
password string
expected bool
}{
{"correct password", "correctpassword", true},
{"wrong password", "wrongpassword", false},
{"empty password", "", false},
{"similar password", "correctpassword1", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := user.CheckPassword(tt.password)
assert.Equal(t, tt.expected, result)
})
}
}
func TestUser_GetFullName(t *testing.T) {
tests := []struct {
name string
user User
expected string
name string
user User
expected string
}{
{
name: "first and last name",
@@ -82,136 +47,7 @@ func TestUser_TableName(t *testing.T) {
assert.Equal(t, "auth_user", user.TableName())
}
func TestAuthToken_TableName(t *testing.T) {
token := AuthToken{}
assert.Equal(t, "user_authtoken", token.TableName())
}
func TestUserProfile_TableName(t *testing.T) {
profile := UserProfile{}
assert.Equal(t, "user_userprofile", profile.TableName())
}
func TestConfirmationCode_IsValid(t *testing.T) {
now := time.Now().UTC()
future := now.Add(1 * time.Hour)
past := now.Add(-1 * time.Hour)
tests := []struct {
name string
code ConfirmationCode
expected bool
}{
{
name: "valid code",
code: ConfirmationCode{IsUsed: false, ExpiresAt: future},
expected: true,
},
{
name: "used code",
code: ConfirmationCode{IsUsed: true, ExpiresAt: future},
expected: false,
},
{
name: "expired code",
code: ConfirmationCode{IsUsed: false, ExpiresAt: past},
expected: false,
},
{
name: "used and expired",
code: ConfirmationCode{IsUsed: true, ExpiresAt: past},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.code.IsValid()
assert.Equal(t, tt.expected, result)
})
}
}
func TestPasswordResetCode_IsValid(t *testing.T) {
now := time.Now().UTC()
future := now.Add(1 * time.Hour)
past := now.Add(-1 * time.Hour)
tests := []struct {
name string
code PasswordResetCode
expected bool
}{
{
name: "valid code",
code: PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 0, MaxAttempts: 5},
expected: true,
},
{
name: "used code",
code: PasswordResetCode{Used: true, ExpiresAt: future, Attempts: 0, MaxAttempts: 5},
expected: false,
},
{
name: "expired code",
code: PasswordResetCode{Used: false, ExpiresAt: past, Attempts: 0, MaxAttempts: 5},
expected: false,
},
{
name: "max attempts reached",
code: PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 5, MaxAttempts: 5},
expected: false,
},
{
name: "attempts under max",
code: PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 4, MaxAttempts: 5},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.code.IsValid()
assert.Equal(t, tt.expected, result)
})
}
}
func TestPasswordResetCode_SetAndCheckCode(t *testing.T) {
code := &PasswordResetCode{}
err := code.SetCode("123456")
require.NoError(t, err)
assert.NotEmpty(t, code.CodeHash)
// Check correct code
assert.True(t, code.CheckCode("123456"))
// Check wrong code
assert.False(t, code.CheckCode("654321"))
assert.False(t, code.CheckCode(""))
}
func TestGenerateConfirmationCode(t *testing.T) {
code := GenerateConfirmationCode()
assert.Len(t, code, 6)
// Generate multiple codes and ensure they're different
codes := make(map[string]bool)
for i := 0; i < 10; i++ {
c := GenerateConfirmationCode()
assert.Len(t, c, 6)
codes[c] = true
}
// Most codes should be unique (very unlikely to have collisions)
assert.Greater(t, len(codes), 5)
}
func TestGenerateResetToken(t *testing.T) {
token := GenerateResetToken()
assert.Len(t, token, 64) // 32 bytes = 64 hex chars
// Ensure uniqueness
token2 := GenerateResetToken()
assert.NotEqual(t, token, token2)
}