feat: #337 unify types for auth module
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 43s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 49s

This commit was merged in pull request #338.
This commit is contained in:
2024-12-18 23:44:59 +01:00
parent dcc5207272
commit fdb955f20c
13 changed files with 259 additions and 305 deletions

View File

@@ -26,55 +26,25 @@ var (
ErrTokenInvalid = errors.New("token is invalid")
)
type User struct {
Id uuid.UUID
Email string
EmailVerified bool
}
func NewUser(user *db.User) *User {
return &User{
Id: user.Id,
Email: user.Email,
EmailVerified: user.EmailVerified,
}
}
type Session struct {
Id string
CreatedAt time.Time
ExpiresAt time.Time
User *User
}
func NewSession(session *db.Session, user *User) *Session {
return &Session{
Id: session.Id,
CreatedAt: session.CreatedAt,
ExpiresAt: session.ExpiresAt,
User: user,
}
}
type Auth interface {
SignUp(email string, password string) (*User, error)
SignUp(email string, password string) (*types.User, error)
SendVerificationMail(userId uuid.UUID, email string)
VerifyUserEmail(token string) error
SignIn(email string, password string) (*Session, error)
SignInSession(sessionId string) (*Session, error)
SignInAnonymous() (*Session, error)
SignIn(email string, password string) (*types.Session, *types.User, error)
SignInSession(sessionId string) (*types.Session, *types.User, error)
SignInAnonymous() (*types.Session, error)
SignOut(sessionId string) error
DeleteAccount(user *User, currPass string) error
DeleteAccount(user *types.User, currPass string) error
ChangePassword(session *Session, currPass, newPass string) error
ChangePassword(user *types.User, sessionId string, currPass, newPass string) error
SendForgotPasswordMail(email string) error
ForgotPassword(token string, newPass string) error
IsCsrfTokenValid(tokenStr string, sessionId string) bool
GetCsrfToken(session *Session) (string, error)
GetCsrfToken(session *types.Session) (string, error)
}
type AuthImpl struct {
@@ -95,69 +65,65 @@ func NewAuthImpl(db db.Auth, random Random, clock Clock, mail Mail, serverSettin
}
}
func (service AuthImpl) SignIn(email string, password string) (*Session, error) {
func (service AuthImpl) SignIn(email string, password string) (*types.Session, *types.User, error) {
user, err := service.db.GetUserByEmail(email)
if err != nil {
if errors.Is(err, db.ErrNotFound) {
return nil, ErrInvalidCredentials
return nil, nil, ErrInvalidCredentials
} else {
return nil, types.ErrInternal
return nil, nil, types.ErrInternal
}
}
hash := GetHashPassword(password, user.Salt)
if subtle.ConstantTimeCompare(hash, user.Password) == 0 {
return nil, ErrInvalidCredentials
return nil, nil, ErrInvalidCredentials
}
session, err := service.createSession(user.Id)
if err != nil {
return nil, types.ErrInternal
return nil, nil, types.ErrInternal
}
return NewSession(session, NewUser(user)), nil
return session, user, nil
}
func (service AuthImpl) SignInSession(sessionId string) (*Session, error) {
func (service AuthImpl) SignInSession(sessionId string) (*types.Session, *types.User, error) {
if sessionId == "" {
return nil, ErrSessionIdInvalid
return nil, nil, ErrSessionIdInvalid
}
sessionDb, err := service.db.GetSession(sessionId)
session, err := service.db.GetSession(sessionId)
if err != nil {
return nil, nil, types.ErrInternal
}
if session.ExpiresAt.Before(service.clock.Now()) {
return nil, nil, nil
}
if session.UserId == uuid.Nil {
return session, nil, nil
}
user, err := service.db.GetUser(session.UserId)
if err != nil {
return nil, nil, types.ErrInternal
}
return session, user, nil
}
func (service AuthImpl) SignInAnonymous() (*types.Session, error) {
session, err := service.createSession(uuid.Nil)
if err != nil {
return nil, types.ErrInternal
}
if sessionDb.ExpiresAt.Before(service.clock.Now()) {
return nil, nil
}
if sessionDb.UserId == uuid.Nil {
return NewSession(sessionDb, nil), nil
}
userDb, err := service.db.GetUser(sessionDb.UserId)
if err != nil {
return nil, types.ErrInternal
}
user := NewUser(userDb)
session := NewSession(sessionDb, user)
return session, nil
}
func (service AuthImpl) SignInAnonymous() (*Session, error) {
sessionDb, err := service.createSession(uuid.Nil)
if err != nil {
return nil, types.ErrInternal
}
return NewSession(sessionDb, nil), nil
}
func (service AuthImpl) createSession(userId uuid.UUID) (*db.Session, error) {
func (service AuthImpl) createSession(userId uuid.UUID) (*types.Session, error) {
sessionId, err := service.random.String(32)
if err != nil {
return nil, types.ErrInternal
@@ -172,7 +138,7 @@ func (service AuthImpl) createSession(userId uuid.UUID) (*db.Session, error) {
createAt := service.clock.Now()
expiresAt := createAt.Add(24 * time.Hour)
session := db.NewSession(sessionId, userId, createAt, expiresAt)
session := types.NewSession(sessionId, userId, createAt, expiresAt)
err = service.db.InsertSession(session)
if err != nil {
@@ -182,7 +148,7 @@ func (service AuthImpl) createSession(userId uuid.UUID) (*db.Session, error) {
return session, nil
}
func (service AuthImpl) SignUp(email string, password string) (*User, error) {
func (service AuthImpl) SignUp(email string, password string) (*types.User, error) {
_, err := mail.ParseAddress(email)
if err != nil {
return nil, ErrInvalidEmail
@@ -204,9 +170,9 @@ func (service AuthImpl) SignUp(email string, password string) (*User, error) {
hash := GetHashPassword(password, salt)
dbUser := db.NewUser(userId, email, false, nil, false, hash, salt, service.clock.Now())
user := types.NewUser(userId, email, false, nil, false, hash, salt, service.clock.Now())
err = service.db.InsertUser(dbUser)
err = service.db.InsertUser(user)
if err != nil {
if err == db.ErrAlreadyExists {
return nil, ErrAccountExists
@@ -215,17 +181,17 @@ func (service AuthImpl) SignUp(email string, password string) (*User, error) {
}
}
return NewUser(dbUser), nil
return user, nil
}
func (service AuthImpl) SendVerificationMail(userId uuid.UUID, email string) {
tokens, err := service.db.GetTokensByUserIdAndType(userId, db.TokenTypeEmailVerify)
tokens, err := service.db.GetTokensByUserIdAndType(userId, types.TokenTypeEmailVerify)
if err != nil && err != db.ErrNotFound {
return
}
var token *db.Token
var token *types.Token
if len(tokens) > 0 {
token = tokens[0]
@@ -237,7 +203,7 @@ func (service AuthImpl) SendVerificationMail(userId uuid.UUID, email string) {
return
}
token = db.NewToken(userId, "", newTokenStr, db.TokenTypeEmailVerify, service.clock.Now(), service.clock.Now().Add(24*time.Hour))
token = types.NewToken(userId, "", newTokenStr, types.TokenTypeEmailVerify, service.clock.Now(), service.clock.Now().Add(24*time.Hour))
err = service.db.InsertToken(token)
if err != nil {
@@ -271,7 +237,7 @@ func (service AuthImpl) VerifyUserEmail(tokenStr string) error {
return types.ErrInternal
}
if token.Type != db.TokenTypeEmailVerify {
if token.Type != types.TokenTypeEmailVerify {
return types.ErrInternal
}
@@ -298,7 +264,7 @@ func (service AuthImpl) SignOut(sessionId string) error {
return service.db.DeleteSession(sessionId)
}
func (service AuthImpl) DeleteAccount(user *User, currPass string) error {
func (service AuthImpl) DeleteAccount(user *types.User, currPass string) error {
userDb, err := service.db.GetUser(user.Id)
if err != nil {
@@ -320,7 +286,7 @@ func (service AuthImpl) DeleteAccount(user *User, currPass string) error {
return nil
}
func (service AuthImpl) ChangePassword(session *Session, currPass, newPass string) error {
func (service AuthImpl) ChangePassword(user *types.User, sessionId string, currPass, newPass string) error {
if !isPasswordValid(newPass) {
return ErrInvalidPassword
@@ -330,31 +296,26 @@ func (service AuthImpl) ChangePassword(session *Session, currPass, newPass strin
return ErrInvalidPassword
}
userDb, err := service.db.GetUser(session.User.Id)
if err != nil {
return err
}
currHash := GetHashPassword(currPass, user.Salt)
currHash := GetHashPassword(currPass, userDb.Salt)
if subtle.ConstantTimeCompare(currHash, userDb.Password) == 0 {
if subtle.ConstantTimeCompare(currHash, user.Password) == 0 {
return ErrInvalidCredentials
}
newHash := GetHashPassword(newPass, userDb.Salt)
userDb.Password = newHash
newHash := GetHashPassword(newPass, user.Salt)
user.Password = newHash
err = service.db.UpdateUser(userDb)
err := service.db.UpdateUser(user)
if err != nil {
return err
}
sessions, err := service.db.GetSessions(userDb.Id)
sessions, err := service.db.GetSessions(user.Id)
if err != nil {
return types.ErrInternal
}
for _, s := range sessions {
if s.Id != session.Id {
if s.Id != sessionId {
err = service.db.DeleteSession(s.Id)
if err != nil {
return types.ErrInternal
@@ -380,7 +341,7 @@ func (service AuthImpl) SendForgotPasswordMail(email string) error {
}
}
token := db.NewToken(user.Id, "", tokenStr, db.TokenTypePasswordReset, service.clock.Now(), service.clock.Now().Add(15*time.Minute))
token := types.NewToken(user.Id, "", tokenStr, types.TokenTypePasswordReset, service.clock.Now(), service.clock.Now().Add(15*time.Minute))
err = service.db.InsertToken(token)
if err != nil {
@@ -414,7 +375,7 @@ func (service AuthImpl) ForgotPassword(tokenStr string, newPass string) error {
return err
}
if token.Type != db.TokenTypePasswordReset ||
if token.Type != types.TokenTypePasswordReset ||
token.ExpiresAt.Before(service.clock.Now()) {
return ErrTokenInvalid
}
@@ -454,7 +415,7 @@ func (service AuthImpl) IsCsrfTokenValid(tokenStr string, sessionId string) bool
return false
}
if token.Type != db.TokenTypeCsrf ||
if token.Type != types.TokenTypeCsrf ||
token.SessionId != sessionId ||
token.ExpiresAt.Before(service.clock.Now()) {
@@ -464,12 +425,12 @@ func (service AuthImpl) IsCsrfTokenValid(tokenStr string, sessionId string) bool
return true
}
func (service AuthImpl) GetCsrfToken(session *Session) (string, error) {
func (service AuthImpl) GetCsrfToken(session *types.Session) (string, error) {
if session == nil {
return "", types.ErrInternal
}
tokens, _ := service.db.GetTokensBySessionIdAndType(session.Id, db.TokenTypeCsrf)
tokens, _ := service.db.GetTokensBySessionIdAndType(session.Id, types.TokenTypeCsrf)
if len(tokens) > 0 {
return tokens[0].Token, nil
@@ -480,7 +441,7 @@ func (service AuthImpl) GetCsrfToken(session *Session) (string, error) {
return "", types.ErrInternal
}
token := db.NewToken(uuid.Nil, session.Id, tokenStr, db.TokenTypeCsrf, service.clock.Now(), service.clock.Now().Add(24*time.Hour))
token := types.NewToken(uuid.Nil, session.Id, tokenStr, types.TokenTypeCsrf, service.clock.Now(), service.clock.Now().Add(24*time.Hour))
err = service.db.InsertToken(token)
if err != nil {
return "", types.ErrInternal

View File

@@ -22,7 +22,7 @@ func TestSignIn(t *testing.T) {
t.Parallel()
salt := []byte("salt")
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
user := db.NewUser(
user := types.NewUser(
uuid.New(),
"test@test.de",
true,
@@ -33,12 +33,12 @@ func TestSignIn(t *testing.T) {
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
)
dbSession := db.NewSession("sessionId", user.Id, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC))
session := types.NewSession("sessionId", user.Id, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC))
mockAuthDb := mocks.NewMockAuth(t)
mockAuthDb.EXPECT().GetUserByEmail("test@test.de").Return(user, nil)
mockAuthDb.EXPECT().DeleteOldSessions(user.Id).Return(nil)
mockAuthDb.EXPECT().InsertSession(dbSession).Return(nil)
mockAuthDb.EXPECT().InsertSession(session).Return(nil)
mockRandom := mocks.NewMockRandom(t)
mockRandom.EXPECT().String(32).Return("sessionId", nil)
mockClock := mocks.NewMockClock(t)
@@ -47,11 +47,11 @@ func TestSignIn(t *testing.T) {
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
actualSession, err := underTest.SignIn(user.Email, "password")
actualSession, actualUser, err := underTest.SignIn(user.Email, "password")
assert.Nil(t, err)
expectedSession := NewSession(dbSession, NewUser(user))
assert.Equal(t, expectedSession, actualSession)
assert.Equal(t, session, actualSession)
assert.Equal(t, user, actualUser)
})
t.Run("should return ErrInvalidCretentials if password is not correct", func(t *testing.T) {
@@ -59,7 +59,7 @@ func TestSignIn(t *testing.T) {
salt := []byte("salt")
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
user := db.NewUser(
user := types.NewUser(
uuid.New(),
"test@test.de",
true,
@@ -78,7 +78,7 @@ func TestSignIn(t *testing.T) {
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
_, err := underTest.SignIn("test@test.de", "wrong password")
_, _, err := underTest.SignIn("test@test.de", "wrong password")
assert.Equal(t, ErrInvalidCredentials, err)
})
@@ -93,7 +93,7 @@ func TestSignIn(t *testing.T) {
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
_, err := underTest.SignIn("test", "test")
_, _, err := underTest.SignIn("test", "test")
assert.Equal(t, ErrInvalidCredentials, err)
})
t.Run("should forward ErrInternal on any other error", func(t *testing.T) {
@@ -107,7 +107,7 @@ func TestSignIn(t *testing.T) {
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
_, err := underTest.SignIn("test", "test")
_, _, err := underTest.SignIn("test", "test")
assert.Equal(t, types.ErrInternal, err)
})
@@ -159,33 +159,25 @@ func TestSignUp(t *testing.T) {
mockClock := mocks.NewMockClock(t)
mockMail := mocks.NewMockMail(t)
expected := User{
Id: uuid.New(),
Email: "some@valid.email",
EmailVerified: false,
}
random := NewRandomImpl()
salt, err := random.Bytes(16)
assert.Nil(t, err)
userId := uuid.New()
email := "mail@mail.de"
password := "SomeStrongPassword123!"
mockRandom.EXPECT().UUID().Return(expected.Id, nil)
mockRandom.EXPECT().Bytes(16).Return(salt, nil)
salt := []byte("salt")
createTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
mockClock.EXPECT().Now().Return(createTime)
expected := types.NewUser(userId, email, false, nil, false, GetHashPassword(password, salt), salt, createTime)
mockAuthDb.EXPECT().InsertUser(db.NewUser(expected.Id, expected.Email, false, nil, false, GetHashPassword(password, salt), salt, createTime)).Return(nil)
mockRandom.EXPECT().UUID().Return(userId, nil)
mockRandom.EXPECT().Bytes(16).Return(salt, nil)
mockClock.EXPECT().Now().Return(createTime)
mockAuthDb.EXPECT().InsertUser(expected).Return(nil)
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
actual, err := underTest.SignUp(expected.Email, password)
actual, err := underTest.SignUp(email, password)
assert.Nil(t, err)
assert.Equal(t, expected, *actual)
assert.Equal(t, expected, actual)
})
t.Run("should return ErrAccountExists", func(t *testing.T) {
t.Parallel()
@@ -195,28 +187,22 @@ func TestSignUp(t *testing.T) {
mockClock := mocks.NewMockClock(t)
mockMail := mocks.NewMockMail(t)
user := User{
Id: uuid.New(),
Email: "some@valid.email",
}
random := NewRandomImpl()
salt, err := random.Bytes(16)
assert.Nil(t, err)
userId := uuid.New()
email := "some@valid.email"
createTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
password := "SomeStrongPassword123!"
salt := []byte("salt")
user := types.NewUser(userId, email, false, nil, false, GetHashPassword(password, salt), salt, createTime)
mockRandom.EXPECT().UUID().Return(user.Id, nil)
mockRandom.EXPECT().Bytes(16).Return(salt, nil)
createTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
mockClock.EXPECT().Now().Return(createTime)
mockAuthDb.EXPECT().InsertUser(db.NewUser(user.Id, user.Email, false, nil, false, GetHashPassword(password, salt), salt, createTime)).Return(db.ErrAlreadyExists)
mockAuthDb.EXPECT().InsertUser(user).Return(db.ErrAlreadyExists)
underTest := NewAuthImpl(mockAuthDb, mockRandom, mockClock, mockMail, &types.Settings{})
_, err = underTest.SignUp(user.Email, password)
_, err := underTest.SignUp(user.Email, password)
assert.Equal(t, ErrAccountExists, err)
})
}
@@ -227,8 +213,8 @@ func TestSendVerificationMail(t *testing.T) {
t.Run("should use stored token and send mail", func(t *testing.T) {
t.Parallel()
token := db.NewToken(uuid.New(), "sessionId", "someRandomTokenToUse", db.TokenTypeEmailVerify, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC))
tokens := []*db.Token{token}
token := types.NewToken(uuid.New(), "sessionId", "someRandomTokenToUse", types.TokenTypeEmailVerify, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC))
tokens := []*types.Token{token}
email := "some@email.de"
userId := uuid.New()
@@ -238,7 +224,7 @@ func TestSendVerificationMail(t *testing.T) {
mockClock := mocks.NewMockClock(t)
mockMail := mocks.NewMockMail(t)
mockAuthDb.EXPECT().GetTokensByUserIdAndType(userId, db.TokenTypeEmailVerify).Return(tokens, nil)
mockAuthDb.EXPECT().GetTokensByUserIdAndType(userId, types.TokenTypeEmailVerify).Return(tokens, nil)
mockMail.EXPECT().SendMail(email, "Welcome to ME-FIT", mock.MatchedBy(func(message string) bool {
return strings.Contains(message, token.Token)

View File

@@ -10,9 +10,9 @@ import (
)
type Workout interface {
AddWorkout(user *User, workoutDto *WorkoutDto) (*WorkoutDto, error)
DeleteWorkout(user *User, rowId int) error
GetWorkouts(user *User) ([]*WorkoutDto, error)
AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error)
DeleteWorkout(user *types.User, rowId int) error
GetWorkouts(user *types.User) ([]*WorkoutDto, error)
}
type WorkoutImpl struct {
@@ -64,7 +64,7 @@ var (
ErrInputValues = errors.New("invalid input values")
)
func (service WorkoutImpl) AddWorkout(user *User, workoutDto *WorkoutDto) (*WorkoutDto, error) {
func (service WorkoutImpl) AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error) {
if workoutDto.Date == "" || workoutDto.Type == "" || workoutDto.Sets == "" || workoutDto.Reps == "" {
return nil, ErrInputValues
@@ -95,7 +95,7 @@ func (service WorkoutImpl) AddWorkout(user *User, workoutDto *WorkoutDto) (*Work
return NewWorkoutDtoFromDb(workout), nil
}
func (service WorkoutImpl) DeleteWorkout(user *User, rowId int) error {
func (service WorkoutImpl) DeleteWorkout(user *types.User, rowId int) error {
if user == nil {
return types.ErrInternal
}
@@ -103,7 +103,7 @@ func (service WorkoutImpl) DeleteWorkout(user *User, rowId int) error {
return service.db.DeleteWorkout(user.Id, rowId)
}
func (service WorkoutImpl) GetWorkouts(user *User) ([]*WorkoutDto, error) {
func (service WorkoutImpl) GetWorkouts(user *types.User) ([]*WorkoutDto, error) {
if user == nil {
return nil, types.ErrInternal
}