feat: Refactor User entity and datasource to use email and password hashing with salt
This commit is contained in:
parent
f567d086ec
commit
3193204dac
31
backend/internal/domain/entities/errors.go
Normal file
31
backend/internal/domain/entities/errors.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
|
var ErrUserNotFound = errors.New("user not found")
|
||||||
|
var ErrActivityNotFound = errors.New("activity not found")
|
||||||
|
var ErrActivityAlreadyExists = errors.New("activity already exists")
|
||||||
|
var ErrInvalidPassword = errors.New("invalid password")
|
||||||
|
var ErrInvalidEmail = errors.New("invalid email")
|
||||||
|
var ErrInvalidUsername = errors.New("invalid username")
|
||||||
|
var ErrInvalidRole = errors.New("invalid role")
|
||||||
|
var ErrInvalidCompanyID = errors.New("invalid company id")
|
||||||
|
var ErrInvalidHourlyRate = errors.New("invalid hourly rate")
|
||||||
|
var ErrInvalidID = errors.New("invalid id")
|
||||||
|
var ErrTimeEntryNotFound = errors.New("time entry not found")
|
||||||
|
var ErrTimeEntryAlreadyExists = errors.New("time entry already exists")
|
||||||
|
var ErrInvalidDuration = errors.New("invalid duration")
|
||||||
|
var ErrInvalidDescription = errors.New("invalid description")
|
||||||
|
var ErrInvalidStartTime = errors.New("invalid start time")
|
||||||
|
var ErrInvalidEndTime = errors.New("invalid end time")
|
||||||
|
var ErrInvalidBillable = errors.New("invalid billable")
|
||||||
|
var ErrInvalidProjectID = errors.New("invalid project id")
|
||||||
|
var ErrProjectNotFound = errors.New("project not found")
|
||||||
|
var ErrProjectAlreadyExists = errors.New("project already exists")
|
||||||
|
var ErrInvalidName = errors.New("invalid name")
|
||||||
|
var ErrInvalidClientID = errors.New("invalid client id")
|
||||||
|
var ErrClientNotFound = errors.New("client not found")
|
||||||
|
var ErrClientAlreadyExists = errors.New("client already exists")
|
||||||
|
var ErrInvalidAddress = errors.New("invalid address")
|
||||||
|
var ErrInvalidPhone = errors.New("invalid phone")
|
@ -4,15 +4,15 @@ import "github.com/oklog/ulid/v2"
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
EntityBase
|
EntityBase
|
||||||
Username string
|
Email string
|
||||||
Password string
|
Salt string
|
||||||
Role string
|
Role string
|
||||||
CompanyID int
|
CompanyID int
|
||||||
HourlyRate float64
|
HourlyRate float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserCreate struct {
|
type UserCreate struct {
|
||||||
Username string
|
Email string
|
||||||
Password string
|
Password string
|
||||||
Role string
|
Role string
|
||||||
CompanyID int
|
CompanyID int
|
||||||
@ -21,7 +21,7 @@ type UserCreate struct {
|
|||||||
|
|
||||||
type UserUpdate struct {
|
type UserUpdate struct {
|
||||||
ID ulid.ULID
|
ID ulid.ULID
|
||||||
Username *string
|
Email *string
|
||||||
Password *string
|
Password *string
|
||||||
Role *string
|
Role *string
|
||||||
CompanyID *int
|
CompanyID *int
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
type UserDatasource interface {
|
type UserDatasource interface {
|
||||||
Get(ctx context.Context, id ulid.ULID) (*entities.User, error)
|
Get(ctx context.Context, id ulid.ULID) (*entities.User, error)
|
||||||
Create(ctx context.Context, user *entities.User) error
|
Create(ctx context.Context, user *entities.User, passwordHash string, salt string) error
|
||||||
Update(ctx context.Context, user *entities.User) error
|
Update(ctx context.Context, user *entities.User, passwordHash *string) error
|
||||||
Delete(ctx context.Context, id ulid.ULID) error
|
Delete(ctx context.Context, id ulid.ULID) error
|
||||||
GetByUsername(ctx context.Context, username string) (*entities.User, error)
|
GetByEmail(ctx context.Context, email string) (*entities.User, error)
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,9 @@ type UserDBO struct {
|
|||||||
CreatedAt time.Time `gorm:"not null"`
|
CreatedAt time.Time `gorm:"not null"`
|
||||||
UpdatedAt time.Time `gorm:"not null"`
|
UpdatedAt time.Time `gorm:"not null"`
|
||||||
LastEditorID ulid.ULID
|
LastEditorID ulid.ULID
|
||||||
Username string
|
Email string
|
||||||
Password string
|
PasswordHash string
|
||||||
|
Salt string
|
||||||
Role string
|
Role string
|
||||||
CompanyID int
|
CompanyID int
|
||||||
HourlyRate float64
|
HourlyRate float64
|
||||||
|
@ -61,20 +61,23 @@ func (r *TimeEntryDatasource) Create(ctx context.Context, timeEntry *entities.Ti
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *TimeEntryDatasource) Update(ctx context.Context, timeEntry *entities.TimeEntry) error {
|
func (r *TimeEntryDatasource) Update(ctx context.Context, timeEntry *entities.TimeEntry) error {
|
||||||
timeEntryDBO := dbo.TimeEntryDBO{
|
var existingEntry dbo.TimeEntryDBO
|
||||||
ID: timeEntry.ID,
|
if err := r.db.WithContext(ctx).First(&existingEntry, "id = ?", timeEntry.ID).Error; err != nil {
|
||||||
CreatedAt: timeEntry.CreatedAt,
|
return entities.ErrTimeEntryNotFound
|
||||||
UpdatedAt: timeEntry.UpdatedAt,
|
|
||||||
UserID: timeEntry.UserID,
|
|
||||||
ProjectID: timeEntry.ProjectID,
|
|
||||||
ActivityID: timeEntry.ActivityID,
|
|
||||||
Start: timeEntry.Start,
|
|
||||||
End: timeEntry.End,
|
|
||||||
Description: timeEntry.Description,
|
|
||||||
Billable: timeEntry.Billable,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.db.WithContext(ctx).Save(&timeEntryDBO).Error
|
updateData := map[string]any{
|
||||||
|
"user_id": timeEntry.UserID,
|
||||||
|
"project_id": timeEntry.ProjectID,
|
||||||
|
"activity_id": timeEntry.ActivityID,
|
||||||
|
"start": timeEntry.Start,
|
||||||
|
"end": timeEntry.End,
|
||||||
|
"description": timeEntry.Description,
|
||||||
|
"billable": timeEntry.Billable,
|
||||||
|
"updated_at": gorm.Expr("NOW()"), // Optional: Automatisches Update-Datum
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.db.WithContext(ctx).Model(&dbo.TimeEntryDBO{}).Where("id = ?", timeEntry.ID).Updates(updateData).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TimeEntryDatasource) Delete(ctx context.Context, id ulid.ULID) error {
|
func (r *TimeEntryDatasource) Delete(ctx context.Context, id ulid.ULID) error {
|
||||||
|
@ -30,8 +30,8 @@ func (r *UserDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.User,
|
|||||||
CreatedAt: userDBO.CreatedAt,
|
CreatedAt: userDBO.CreatedAt,
|
||||||
UpdatedAt: userDBO.UpdatedAt,
|
UpdatedAt: userDBO.UpdatedAt,
|
||||||
},
|
},
|
||||||
Username: userDBO.Username,
|
Email: userDBO.Email,
|
||||||
Password: userDBO.Password,
|
Salt: userDBO.Salt,
|
||||||
Role: userDBO.Role,
|
Role: userDBO.Role,
|
||||||
CompanyID: userDBO.CompanyID,
|
CompanyID: userDBO.CompanyID,
|
||||||
HourlyRate: userDBO.HourlyRate,
|
HourlyRate: userDBO.HourlyRate,
|
||||||
@ -40,43 +40,57 @@ func (r *UserDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.User,
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserDatasource) Create(ctx context.Context, user *entities.User) error {
|
func (r *UserDatasource) Create(ctx context.Context, user *entities.User, passwordHash string, salt string) error {
|
||||||
|
|
||||||
|
old := r.db.WithContext(ctx).First(&dbo.UserDBO{}, "email = ?", user.Email)
|
||||||
|
if old.Error == nil {
|
||||||
|
return entities.ErrUserAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
userDBO := dbo.UserDBO{
|
userDBO := dbo.UserDBO{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
CreatedAt: user.CreatedAt,
|
CreatedAt: user.CreatedAt,
|
||||||
UpdatedAt: user.UpdatedAt,
|
UpdatedAt: user.UpdatedAt,
|
||||||
Username: user.Username,
|
Email: user.Email,
|
||||||
Password: user.Password,
|
PasswordHash: passwordHash,
|
||||||
Role: user.Role,
|
Salt: salt,
|
||||||
CompanyID: user.CompanyID,
|
Role: user.Role,
|
||||||
HourlyRate: user.HourlyRate,
|
CompanyID: user.CompanyID,
|
||||||
|
HourlyRate: user.HourlyRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.db.WithContext(ctx).Create(&userDBO).Error
|
return r.db.WithContext(ctx).Create(&userDBO).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserDatasource) Update(ctx context.Context, user *entities.User) error {
|
func (r *UserDatasource) Update(ctx context.Context, user *entities.User, passwordHash *string) error {
|
||||||
userDBO := dbo.UserDBO{
|
var existingUser dbo.UserDBO
|
||||||
ID: user.ID,
|
if err := r.db.WithContext(ctx).First(&existingUser, "id = ?", user.ID).Error; err != nil {
|
||||||
CreatedAt: user.CreatedAt,
|
return entities.ErrUserNotFound
|
||||||
UpdatedAt: user.UpdatedAt,
|
|
||||||
Username: user.Username,
|
|
||||||
Password: user.Password,
|
|
||||||
Role: user.Role,
|
|
||||||
CompanyID: user.CompanyID,
|
|
||||||
HourlyRate: user.HourlyRate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.db.WithContext(ctx).Save(&userDBO).Error
|
// Nur relevante Felder aktualisieren
|
||||||
|
updateData := map[string]interface{}{
|
||||||
|
"email": user.Email,
|
||||||
|
"role": user.Role,
|
||||||
|
"company_id": user.CompanyID,
|
||||||
|
"hourly_rate": user.HourlyRate,
|
||||||
|
"updated_at": gorm.Expr("NOW()"), // Optional: Automatisch das Update-Datum setzen
|
||||||
|
}
|
||||||
|
|
||||||
|
if passwordHash != nil {
|
||||||
|
updateData["password_hash"] = *passwordHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.db.WithContext(ctx).Model(&dbo.UserDBO{}).Where("id = ?", user.ID).Updates(updateData).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserDatasource) Delete(ctx context.Context, id ulid.ULID) error {
|
func (r *UserDatasource) Delete(ctx context.Context, id ulid.ULID) error {
|
||||||
return r.db.WithContext(ctx).Delete(&dbo.UserDBO{}, "id = ?", id).Error
|
return r.db.WithContext(ctx).Delete(&dbo.UserDBO{}, "id = ?", id).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserDatasource) GetByUsername(ctx context.Context, username string) (*entities.User, error) {
|
func (r *UserDatasource) GetByEmail(ctx context.Context, email string) (*entities.User, error) {
|
||||||
var userDBO dbo.UserDBO
|
var userDBO dbo.UserDBO
|
||||||
if err := r.db.WithContext(ctx).Where("username = ?", username).First(&userDBO).Error; err != nil {
|
if err := r.db.WithContext(ctx).Where("email = ?", email).First(&userDBO).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +100,8 @@ func (r *UserDatasource) GetByUsername(ctx context.Context, username string) (*e
|
|||||||
CreatedAt: userDBO.CreatedAt,
|
CreatedAt: userDBO.CreatedAt,
|
||||||
UpdatedAt: userDBO.UpdatedAt,
|
UpdatedAt: userDBO.UpdatedAt,
|
||||||
},
|
},
|
||||||
Username: userDBO.Username,
|
Email: userDBO.Email,
|
||||||
Password: userDBO.Password,
|
Salt: userDBO.Salt,
|
||||||
Role: userDBO.Role,
|
Role: userDBO.Role,
|
||||||
CompanyID: userDBO.CompanyID,
|
CompanyID: userDBO.CompanyID,
|
||||||
HourlyRate: userDBO.HourlyRate,
|
HourlyRate: userDBO.HourlyRate,
|
||||||
|
6
backend/internal/interfaces/http/dto/auth_dto.go
Normal file
6
backend/internal/interfaces/http/dto/auth_dto.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type AuthDto struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
@ -11,16 +11,15 @@ type UserDto struct {
|
|||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
LastEditorID ulid.ULID `json:"lastEditorID"`
|
LastEditorID ulid.ULID `json:"lastEditorID"`
|
||||||
Username string `json:"username"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"` // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
CompanyID int `json:"companyId"`
|
CompanyID int `json:"companyId"`
|
||||||
HourlyRate float64 `json:"hourlyRate"`
|
HourlyRate float64 `json:"hourlyRate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserCreateDto struct {
|
type UserCreateDto struct {
|
||||||
Username string `json:"username"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"` // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
Password string `json:"password"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
CompanyID int `json:"companyId"`
|
CompanyID int `json:"companyId"`
|
||||||
HourlyRate float64 `json:"hourlyRate"`
|
HourlyRate float64 `json:"hourlyRate"`
|
||||||
@ -31,8 +30,8 @@ type UserUpdateDto struct {
|
|||||||
CreatedAt *time.Time `json:"createdAt"`
|
CreatedAt *time.Time `json:"createdAt"`
|
||||||
UpdatedAt *time.Time `json:"updatedAt"`
|
UpdatedAt *time.Time `json:"updatedAt"`
|
||||||
LastEditorID *ulid.ULID `json:"lastEditorID"`
|
LastEditorID *ulid.ULID `json:"lastEditorID"`
|
||||||
Username *string `json:"username"`
|
Email *string `json:"email"`
|
||||||
Password *string `json:"password"` // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
Password *string `json:"password"`
|
||||||
Role *string `json:"role"`
|
Role *string `json:"role"`
|
||||||
CompanyID *int `json:"companyId"`
|
CompanyID *int `json:"companyId"`
|
||||||
HourlyRate *float64 `json:"hourlyRate"`
|
HourlyRate *float64 `json:"hourlyRate"`
|
||||||
|
@ -140,14 +140,14 @@ export interface UserDto {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
lastEditorID: string;
|
lastEditorID: string;
|
||||||
username: string;
|
email: string;
|
||||||
password: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
password: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
||||||
role: string;
|
role: string;
|
||||||
companyId: number /* int */;
|
companyId: number /* int */;
|
||||||
hourlyRate: number /* float64 */;
|
hourlyRate: number /* float64 */;
|
||||||
}
|
}
|
||||||
export interface UserCreateDto {
|
export interface UserCreateDto {
|
||||||
username: string;
|
email: string;
|
||||||
password: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
password: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
||||||
role: string;
|
role: string;
|
||||||
companyId: number /* int */;
|
companyId: number /* int */;
|
||||||
@ -158,7 +158,7 @@ export interface UserUpdateDto {
|
|||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
lastEditorID?: string;
|
lastEditorID?: string;
|
||||||
username?: string;
|
email?: string;
|
||||||
password?: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
password?: string; // Note: In a real application, you would NEVER send the password in a DTO. This is just for demonstration.
|
||||||
role?: string;
|
role?: string;
|
||||||
companyId?: number /* int */;
|
companyId?: number /* int */;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user