diff --git a/starter/cli/create-user.go b/starter/cli/create-user.go new file mode 100644 index 0000000..263c45a --- /dev/null +++ b/starter/cli/create-user.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "flag" + "log" + "time" + + "entgo.io/ent/dialect" + "git.gorlug.de/code/ersteller/starter/ent" + "git.gorlug.de/code/ersteller/starter/ent/user" + "git.gorlug.de/code/ersteller/starter/env" + "golang.org/x/crypto/bcrypt" +) + +// This CLI creates a new user with email and password, or changes an existing user's password. +func main() { + email := flag.String("email", "", "User email (required)") + password := flag.String("password", "", "User password (required)") + changePassword := flag.Bool("change-password", false, "Change password for existing user") + flag.Parse() + + if *email == "" || *password == "" { + log.Fatal("Both -email and -password flags are required") + } + + environment := env.LoadEnvironment() + + // Open mediator database client + dbClient, err := ent.Open(dialect.SQLite, environment.DatabaseUrl, ent.Log(log.Println), ent.Debug()) + if err != nil { + log.Fatalf("failed opening mediator DB: %v", err) + } + defer dbClient.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Hash the password + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost) + if err != nil { + log.Fatalf("failed to hash password: %v", err) + } + + if *changePassword { + // Change password for existing user + u, err := dbClient.User. + Query(). + Where(user.EmailEQ(*email)). + Only(ctx) + if err != nil { + log.Fatalf("failed to find user with email %s: %v", *email, err) + } + + u, err = u. + Update(). + SetPasswordHash(string(hashedPassword)). + Save(ctx) + if err != nil { + log.Fatalf("failed to update password: %v", err) + } + + log.Printf("Successfully changed password for user: ID=%d, Email=%s\n", u.ID, u.Email) + } else { + // Create the user + u, err := dbClient.User. + Create(). + SetEmail(*email). + SetPasswordHash(string(hashedPassword)). + Save(ctx) + if err != nil { + log.Fatalf("failed to create user: %v", err) + } + + log.Printf("Successfully created user: ID=%d, Email=%s\n", u.ID, u.Email) + } +} diff --git a/starter/ent/client.go b/starter/ent/client.go index fcffd02..50df49e 100644 --- a/starter/ent/client.go +++ b/starter/ent/client.go @@ -11,14 +11,13 @@ import ( "git.gorlug.de/code/ersteller/starter/ent/migrate" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" - "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // Client is the client that holds all ent builders. diff --git a/starter/ent/ent.go b/starter/ent/ent.go index a8149ae..52ee5ba 100644 --- a/starter/ent/ent.go +++ b/starter/ent/ent.go @@ -6,15 +6,15 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "reflect" "sync" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // ent aliases to avoid import conflicts in user's code. diff --git a/starter/ent/enttest/enttest.go b/starter/ent/enttest/enttest.go index 32dbc28..1a1a8b9 100644 --- a/starter/ent/enttest/enttest.go +++ b/starter/ent/enttest/enttest.go @@ -9,9 +9,8 @@ import ( // required by schema hooks. _ "git.gorlug.de/code/ersteller/starter/ent/runtime" - "git.gorlug.de/code/ersteller/starter/ent/migrate" - "entgo.io/ent/dialect/sql/schema" + "git.gorlug.de/code/ersteller/starter/ent/migrate" ) type ( diff --git a/starter/ent/googleauth.go b/starter/ent/googleauth.go index 00fa91a..644f07e 100644 --- a/starter/ent/googleauth.go +++ b/starter/ent/googleauth.go @@ -5,14 +5,14 @@ package ent import ( "encoding/json" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/schema" - "git.gorlug.de/code/ersteller/starter/ent/user" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/schema" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // GoogleAuth is the model entity for the GoogleAuth schema. diff --git a/starter/ent/googleauth/where.go b/starter/ent/googleauth/where.go index 0311830..6c0a0e0 100644 --- a/starter/ent/googleauth/where.go +++ b/starter/ent/googleauth/where.go @@ -3,11 +3,11 @@ package googleauth import ( - "git.gorlug.de/code/ersteller/starter/ent/predicate" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "git.gorlug.de/code/ersteller/starter/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/starter/ent/googleauth_create.go b/starter/ent/googleauth_create.go index 381a0cb..9d47e93 100644 --- a/starter/ent/googleauth_create.go +++ b/starter/ent/googleauth_create.go @@ -6,13 +6,13 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/schema" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/schema" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // GoogleAuthCreate is the builder for creating a GoogleAuth entity. diff --git a/starter/ent/googleauth_delete.go b/starter/ent/googleauth_delete.go index 801a0d4..59d3676 100644 --- a/starter/ent/googleauth_delete.go +++ b/starter/ent/googleauth_delete.go @@ -4,12 +4,12 @@ package ent import ( "context" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/predicate" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/predicate" ) // GoogleAuthDelete is the builder for deleting a GoogleAuth entity. diff --git a/starter/ent/googleauth_query.go b/starter/ent/googleauth_query.go index 7f44f5d..3175593 100644 --- a/starter/ent/googleauth_query.go +++ b/starter/ent/googleauth_query.go @@ -5,15 +5,15 @@ package ent import ( "context" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/user" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // GoogleAuthQuery is the builder for querying GoogleAuth entities. diff --git a/starter/ent/googleauth_update.go b/starter/ent/googleauth_update.go index eaa5f8b..174163f 100644 --- a/starter/ent/googleauth_update.go +++ b/starter/ent/googleauth_update.go @@ -6,15 +6,15 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/schema" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/schema" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // GoogleAuthUpdate is the builder for updating GoogleAuth entities. diff --git a/starter/ent/hook/hook.go b/starter/ent/hook/hook.go index 408acf4..3df51fe 100644 --- a/starter/ent/hook/hook.go +++ b/starter/ent/hook/hook.go @@ -5,6 +5,7 @@ package hook import ( "context" "fmt" + "git.gorlug.de/code/ersteller/starter/ent" ) diff --git a/starter/ent/migrate/schema.go b/starter/ent/migrate/schema.go index b81de80..0dd277f 100644 --- a/starter/ent/migrate/schema.go +++ b/starter/ent/migrate/schema.go @@ -58,8 +58,8 @@ var ( {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, - {Name: "email", Type: field.TypeString, Default: "unknown@localhost"}, - {Name: "password", Type: field.TypeString, Default: ""}, + {Name: "email", Type: field.TypeString, Unique: true}, + {Name: "password_hash", Type: field.TypeString, Default: ""}, } // UsersTable holds the schema information for the "users" table. UsersTable = &schema.Table{ diff --git a/starter/ent/mutation.go b/starter/ent/mutation.go index 4c4573d..ed2a098 100644 --- a/starter/ent/mutation.go +++ b/starter/ent/mutation.go @@ -6,16 +6,16 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/googleauth" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/schema" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "sync" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/schema" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) const ( @@ -1097,7 +1097,7 @@ type UserMutation struct { created_at *time.Time updated_at *time.Time email *string - password *string + password_hash *string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*User, error) @@ -1310,40 +1310,40 @@ func (m *UserMutation) ResetEmail() { m.email = nil } -// SetPassword sets the "password" field. -func (m *UserMutation) SetPassword(s string) { - m.password = &s +// SetPasswordHash sets the "password_hash" field. +func (m *UserMutation) SetPasswordHash(s string) { + m.password_hash = &s } -// Password returns the value of the "password" field in the mutation. -func (m *UserMutation) Password() (r string, exists bool) { - v := m.password +// PasswordHash returns the value of the "password_hash" field in the mutation. +func (m *UserMutation) PasswordHash() (r string, exists bool) { + v := m.password_hash if v == nil { return } return *v, true } -// OldPassword returns the old "password" field's value of the User entity. +// OldPasswordHash returns the old "password_hash" field's value of the User entity. // If the User object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) { +func (m *UserMutation) OldPasswordHash(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldPassword is only allowed on UpdateOne operations") + return v, errors.New("OldPasswordHash is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldPassword requires an ID field in the mutation") + return v, errors.New("OldPasswordHash requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldPassword: %w", err) + return v, fmt.Errorf("querying old value for OldPasswordHash: %w", err) } - return oldValue.Password, nil + return oldValue.PasswordHash, nil } -// ResetPassword resets all changes to the "password" field. -func (m *UserMutation) ResetPassword() { - m.password = nil +// ResetPasswordHash resets all changes to the "password_hash" field. +func (m *UserMutation) ResetPasswordHash() { + m.password_hash = nil } // Where appends a list predicates to the UserMutation builder. @@ -1390,8 +1390,8 @@ func (m *UserMutation) Fields() []string { if m.email != nil { fields = append(fields, user.FieldEmail) } - if m.password != nil { - fields = append(fields, user.FieldPassword) + if m.password_hash != nil { + fields = append(fields, user.FieldPasswordHash) } return fields } @@ -1407,8 +1407,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.UpdatedAt() case user.FieldEmail: return m.Email() - case user.FieldPassword: - return m.Password() + case user.FieldPasswordHash: + return m.PasswordHash() } return nil, false } @@ -1424,8 +1424,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldUpdatedAt(ctx) case user.FieldEmail: return m.OldEmail(ctx) - case user.FieldPassword: - return m.OldPassword(ctx) + case user.FieldPasswordHash: + return m.OldPasswordHash(ctx) } return nil, fmt.Errorf("unknown User field %s", name) } @@ -1456,12 +1456,12 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetEmail(v) return nil - case user.FieldPassword: + case user.FieldPasswordHash: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetPassword(v) + m.SetPasswordHash(v) return nil } return fmt.Errorf("unknown User field %s", name) @@ -1521,8 +1521,8 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldEmail: m.ResetEmail() return nil - case user.FieldPassword: - m.ResetPassword() + case user.FieldPasswordHash: + m.ResetPasswordHash() return nil } return fmt.Errorf("unknown User field %s", name) diff --git a/starter/ent/runtime.go b/starter/ent/runtime.go index c14d934..e4d32d8 100644 --- a/starter/ent/runtime.go +++ b/starter/ent/runtime.go @@ -3,11 +3,12 @@ package ent import ( + "time" + "git.gorlug.de/code/ersteller/starter/ent/googleauth" "git.gorlug.de/code/ersteller/starter/ent/schema" "git.gorlug.de/code/ersteller/starter/ent/todo" "git.gorlug.de/code/ersteller/starter/ent/user" - "time" ) // The init function reads all schema descriptors with runtime code @@ -67,12 +68,8 @@ func init() { user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time) - // userDescEmail is the schema descriptor for email field. - userDescEmail := userFields[0].Descriptor() - // user.DefaultEmail holds the default value on creation for the email field. - user.DefaultEmail = userDescEmail.Default.(string) - // userDescPassword is the schema descriptor for password field. - userDescPassword := userFields[1].Descriptor() - // user.DefaultPassword holds the default value on creation for the password field. - user.DefaultPassword = userDescPassword.Default.(string) + // userDescPasswordHash is the schema descriptor for password_hash field. + userDescPasswordHash := userFields[1].Descriptor() + // user.DefaultPasswordHash holds the default value on creation for the password_hash field. + user.DefaultPasswordHash = userDescPasswordHash.Default.(string) } diff --git a/starter/ent/runtime/runtime.go b/starter/ent/runtime/runtime.go index a742dfd..da8fc91 100644 --- a/starter/ent/runtime/runtime.go +++ b/starter/ent/runtime/runtime.go @@ -2,7 +2,7 @@ package runtime -// The schema-stitching logic is generated in ersteller-lib/starter/ent/runtime.go +// The schema-stitching logic is generated in git.gorlug.de/code/ersteller/starter/ent/runtime.go const ( Version = "v0.14.5" // Version of ent codegen. diff --git a/starter/ent/schema/user.go b/starter/ent/schema/user.go index 6fabfe1..910d4b3 100644 --- a/starter/ent/schema/user.go +++ b/starter/ent/schema/user.go @@ -20,7 +20,9 @@ func (User) Mixin() []ent.Mixin { func (User) Fields() []ent.Field { return []ent.Field{ field.String("email"). - Default("unknown@localhost"), - field.String("password").Default(""), + Unique(), + field.String("password_hash"). + Default(""). + Sensitive(), } } diff --git a/starter/ent/todo.go b/starter/ent/todo.go index 4e061b0..f20fe08 100644 --- a/starter/ent/todo.go +++ b/starter/ent/todo.go @@ -4,13 +4,13 @@ package ent import ( "fmt" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // Todo is the model entity for the Todo schema. diff --git a/starter/ent/todo/where.go b/starter/ent/todo/where.go index 79d50df..c716cd4 100644 --- a/starter/ent/todo/where.go +++ b/starter/ent/todo/where.go @@ -3,11 +3,11 @@ package todo import ( - "git.gorlug.de/code/ersteller/starter/ent/predicate" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "git.gorlug.de/code/ersteller/starter/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/starter/ent/todo_create.go b/starter/ent/todo_create.go index 98de879..1754204 100644 --- a/starter/ent/todo_create.go +++ b/starter/ent/todo_create.go @@ -6,12 +6,12 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // TodoCreate is the builder for creating a Todo entity. diff --git a/starter/ent/todo_delete.go b/starter/ent/todo_delete.go index bf27983..b5061e3 100644 --- a/starter/ent/todo_delete.go +++ b/starter/ent/todo_delete.go @@ -4,12 +4,12 @@ package ent import ( "context" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/todo" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/todo" ) // TodoDelete is the builder for deleting a Todo entity. diff --git a/starter/ent/todo_query.go b/starter/ent/todo_query.go index df591b4..09a2c09 100644 --- a/starter/ent/todo_query.go +++ b/starter/ent/todo_query.go @@ -5,15 +5,15 @@ package ent import ( "context" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // TodoQuery is the builder for querying Todo entities. diff --git a/starter/ent/todo_update.go b/starter/ent/todo_update.go index 0b4a1a9..c560cba 100644 --- a/starter/ent/todo_update.go +++ b/starter/ent/todo_update.go @@ -6,14 +6,14 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/todo" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/todo" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // TodoUpdate is the builder for updating Todo entities. diff --git a/starter/ent/user.go b/starter/ent/user.go index 3b6097a..154338d 100644 --- a/starter/ent/user.go +++ b/starter/ent/user.go @@ -4,12 +4,12 @@ package ent import ( "fmt" - "git.gorlug.de/code/ersteller/starter/ent/user" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // User is the model entity for the User schema. @@ -23,8 +23,8 @@ type User struct { UpdatedAt time.Time `json:"updated_at,omitempty"` // Email holds the value of the "email" field. Email string `json:"email,omitempty"` - // Password holds the value of the "password" field. - Password string `json:"password,omitempty"` + // PasswordHash holds the value of the "password_hash" field. + PasswordHash string `json:"-"` selectValues sql.SelectValues } @@ -35,7 +35,7 @@ func (*User) scanValues(columns []string) ([]any, error) { switch columns[i] { case user.FieldID: values[i] = new(sql.NullInt64) - case user.FieldEmail, user.FieldPassword: + case user.FieldEmail, user.FieldPasswordHash: values[i] = new(sql.NullString) case user.FieldCreatedAt, user.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -78,11 +78,11 @@ func (_m *User) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Email = value.String } - case user.FieldPassword: + case user.FieldPasswordHash: if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field password", values[i]) + return fmt.Errorf("unexpected type %T for field password_hash", values[i]) } else if value.Valid { - _m.Password = value.String + _m.PasswordHash = value.String } default: _m.selectValues.Set(columns[i], values[i]) @@ -129,8 +129,7 @@ func (_m *User) String() string { builder.WriteString("email=") builder.WriteString(_m.Email) builder.WriteString(", ") - builder.WriteString("password=") - builder.WriteString(_m.Password) + builder.WriteString("password_hash=") builder.WriteByte(')') return builder.String() } diff --git a/starter/ent/user/user.go b/starter/ent/user/user.go index ffe95dc..579317e 100644 --- a/starter/ent/user/user.go +++ b/starter/ent/user/user.go @@ -19,8 +19,8 @@ const ( FieldUpdatedAt = "updated_at" // FieldEmail holds the string denoting the email field in the database. FieldEmail = "email" - // FieldPassword holds the string denoting the password field in the database. - FieldPassword = "password" + // FieldPasswordHash holds the string denoting the password_hash field in the database. + FieldPasswordHash = "password_hash" // Table holds the table name of the user in the database. Table = "users" ) @@ -31,7 +31,7 @@ var Columns = []string{ FieldCreatedAt, FieldUpdatedAt, FieldEmail, - FieldPassword, + FieldPasswordHash, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -51,10 +51,8 @@ var ( DefaultUpdatedAt func() time.Time // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. UpdateDefaultUpdatedAt func() time.Time - // DefaultEmail holds the default value on creation for the "email" field. - DefaultEmail string - // DefaultPassword holds the default value on creation for the "password" field. - DefaultPassword string + // DefaultPasswordHash holds the default value on creation for the "password_hash" field. + DefaultPasswordHash string ) // OrderOption defines the ordering options for the User queries. @@ -80,7 +78,7 @@ func ByEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEmail, opts...).ToFunc() } -// ByPassword orders the results by the password field. -func ByPassword(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldPassword, opts...).ToFunc() +// ByPasswordHash orders the results by the password_hash field. +func ByPasswordHash(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPasswordHash, opts...).ToFunc() } diff --git a/starter/ent/user/where.go b/starter/ent/user/where.go index 6662918..394d3ae 100644 --- a/starter/ent/user/where.go +++ b/starter/ent/user/where.go @@ -3,10 +3,10 @@ package user import ( - "git.gorlug.de/code/ersteller/starter/ent/predicate" "time" "entgo.io/ent/dialect/sql" + "git.gorlug.de/code/ersteller/starter/ent/predicate" ) // ID filters vertices based on their ID field. @@ -69,9 +69,9 @@ func Email(v string) predicate.User { return predicate.User(sql.FieldEQ(FieldEmail, v)) } -// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ. -func Password(v string) predicate.User { - return predicate.User(sql.FieldEQ(FieldPassword, v)) +// PasswordHash applies equality check predicate on the "password_hash" field. It's identical to PasswordHashEQ. +func PasswordHash(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPasswordHash, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. @@ -219,69 +219,69 @@ func EmailContainsFold(v string) predicate.User { return predicate.User(sql.FieldContainsFold(FieldEmail, v)) } -// PasswordEQ applies the EQ predicate on the "password" field. -func PasswordEQ(v string) predicate.User { - return predicate.User(sql.FieldEQ(FieldPassword, v)) +// PasswordHashEQ applies the EQ predicate on the "password_hash" field. +func PasswordHashEQ(v string) predicate.User { + return predicate.User(sql.FieldEQ(FieldPasswordHash, v)) } -// PasswordNEQ applies the NEQ predicate on the "password" field. -func PasswordNEQ(v string) predicate.User { - return predicate.User(sql.FieldNEQ(FieldPassword, v)) +// PasswordHashNEQ applies the NEQ predicate on the "password_hash" field. +func PasswordHashNEQ(v string) predicate.User { + return predicate.User(sql.FieldNEQ(FieldPasswordHash, v)) } -// PasswordIn applies the In predicate on the "password" field. -func PasswordIn(vs ...string) predicate.User { - return predicate.User(sql.FieldIn(FieldPassword, vs...)) +// PasswordHashIn applies the In predicate on the "password_hash" field. +func PasswordHashIn(vs ...string) predicate.User { + return predicate.User(sql.FieldIn(FieldPasswordHash, vs...)) } -// PasswordNotIn applies the NotIn predicate on the "password" field. -func PasswordNotIn(vs ...string) predicate.User { - return predicate.User(sql.FieldNotIn(FieldPassword, vs...)) +// PasswordHashNotIn applies the NotIn predicate on the "password_hash" field. +func PasswordHashNotIn(vs ...string) predicate.User { + return predicate.User(sql.FieldNotIn(FieldPasswordHash, vs...)) } -// PasswordGT applies the GT predicate on the "password" field. -func PasswordGT(v string) predicate.User { - return predicate.User(sql.FieldGT(FieldPassword, v)) +// PasswordHashGT applies the GT predicate on the "password_hash" field. +func PasswordHashGT(v string) predicate.User { + return predicate.User(sql.FieldGT(FieldPasswordHash, v)) } -// PasswordGTE applies the GTE predicate on the "password" field. -func PasswordGTE(v string) predicate.User { - return predicate.User(sql.FieldGTE(FieldPassword, v)) +// PasswordHashGTE applies the GTE predicate on the "password_hash" field. +func PasswordHashGTE(v string) predicate.User { + return predicate.User(sql.FieldGTE(FieldPasswordHash, v)) } -// PasswordLT applies the LT predicate on the "password" field. -func PasswordLT(v string) predicate.User { - return predicate.User(sql.FieldLT(FieldPassword, v)) +// PasswordHashLT applies the LT predicate on the "password_hash" field. +func PasswordHashLT(v string) predicate.User { + return predicate.User(sql.FieldLT(FieldPasswordHash, v)) } -// PasswordLTE applies the LTE predicate on the "password" field. -func PasswordLTE(v string) predicate.User { - return predicate.User(sql.FieldLTE(FieldPassword, v)) +// PasswordHashLTE applies the LTE predicate on the "password_hash" field. +func PasswordHashLTE(v string) predicate.User { + return predicate.User(sql.FieldLTE(FieldPasswordHash, v)) } -// PasswordContains applies the Contains predicate on the "password" field. -func PasswordContains(v string) predicate.User { - return predicate.User(sql.FieldContains(FieldPassword, v)) +// PasswordHashContains applies the Contains predicate on the "password_hash" field. +func PasswordHashContains(v string) predicate.User { + return predicate.User(sql.FieldContains(FieldPasswordHash, v)) } -// PasswordHasPrefix applies the HasPrefix predicate on the "password" field. -func PasswordHasPrefix(v string) predicate.User { - return predicate.User(sql.FieldHasPrefix(FieldPassword, v)) +// PasswordHashHasPrefix applies the HasPrefix predicate on the "password_hash" field. +func PasswordHashHasPrefix(v string) predicate.User { + return predicate.User(sql.FieldHasPrefix(FieldPasswordHash, v)) } -// PasswordHasSuffix applies the HasSuffix predicate on the "password" field. -func PasswordHasSuffix(v string) predicate.User { - return predicate.User(sql.FieldHasSuffix(FieldPassword, v)) +// PasswordHashHasSuffix applies the HasSuffix predicate on the "password_hash" field. +func PasswordHashHasSuffix(v string) predicate.User { + return predicate.User(sql.FieldHasSuffix(FieldPasswordHash, v)) } -// PasswordEqualFold applies the EqualFold predicate on the "password" field. -func PasswordEqualFold(v string) predicate.User { - return predicate.User(sql.FieldEqualFold(FieldPassword, v)) +// PasswordHashEqualFold applies the EqualFold predicate on the "password_hash" field. +func PasswordHashEqualFold(v string) predicate.User { + return predicate.User(sql.FieldEqualFold(FieldPasswordHash, v)) } -// PasswordContainsFold applies the ContainsFold predicate on the "password" field. -func PasswordContainsFold(v string) predicate.User { - return predicate.User(sql.FieldContainsFold(FieldPassword, v)) +// PasswordHashContainsFold applies the ContainsFold predicate on the "password_hash" field. +func PasswordHashContainsFold(v string) predicate.User { + return predicate.User(sql.FieldContainsFold(FieldPasswordHash, v)) } // And groups predicates with the AND operator between them. diff --git a/starter/ent/user_create.go b/starter/ent/user_create.go index e5c00e9..14f6d9c 100644 --- a/starter/ent/user_create.go +++ b/starter/ent/user_create.go @@ -6,11 +6,11 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // UserCreate is the builder for creating a User entity. @@ -54,24 +54,16 @@ func (_c *UserCreate) SetEmail(v string) *UserCreate { return _c } -// SetNillableEmail sets the "email" field if the given value is not nil. -func (_c *UserCreate) SetNillableEmail(v *string) *UserCreate { - if v != nil { - _c.SetEmail(*v) - } +// SetPasswordHash sets the "password_hash" field. +func (_c *UserCreate) SetPasswordHash(v string) *UserCreate { + _c.mutation.SetPasswordHash(v) return _c } -// SetPassword sets the "password" field. -func (_c *UserCreate) SetPassword(v string) *UserCreate { - _c.mutation.SetPassword(v) - return _c -} - -// SetNillablePassword sets the "password" field if the given value is not nil. -func (_c *UserCreate) SetNillablePassword(v *string) *UserCreate { +// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil. +func (_c *UserCreate) SetNillablePasswordHash(v *string) *UserCreate { if v != nil { - _c.SetPassword(*v) + _c.SetPasswordHash(*v) } return _c } @@ -119,13 +111,9 @@ func (_c *UserCreate) defaults() { v := user.DefaultUpdatedAt() _c.mutation.SetUpdatedAt(v) } - if _, ok := _c.mutation.Email(); !ok { - v := user.DefaultEmail - _c.mutation.SetEmail(v) - } - if _, ok := _c.mutation.Password(); !ok { - v := user.DefaultPassword - _c.mutation.SetPassword(v) + if _, ok := _c.mutation.PasswordHash(); !ok { + v := user.DefaultPasswordHash + _c.mutation.SetPasswordHash(v) } } @@ -140,8 +128,8 @@ func (_c *UserCreate) check() error { if _, ok := _c.mutation.Email(); !ok { return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)} } - if _, ok := _c.mutation.Password(); !ok { - return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)} + if _, ok := _c.mutation.PasswordHash(); !ok { + return &ValidationError{Name: "password_hash", err: errors.New(`ent: missing required field "User.password_hash"`)} } return nil } @@ -181,9 +169,9 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldEmail, field.TypeString, value) _node.Email = value } - if value, ok := _c.mutation.Password(); ok { - _spec.SetField(user.FieldPassword, field.TypeString, value) - _node.Password = value + if value, ok := _c.mutation.PasswordHash(); ok { + _spec.SetField(user.FieldPasswordHash, field.TypeString, value) + _node.PasswordHash = value } return _node, _spec } diff --git a/starter/ent/user_delete.go b/starter/ent/user_delete.go index bc9259d..65e252d 100644 --- a/starter/ent/user_delete.go +++ b/starter/ent/user_delete.go @@ -4,12 +4,12 @@ package ent import ( "context" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/user" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // UserDelete is the builder for deleting a User entity. diff --git a/starter/ent/user_query.go b/starter/ent/user_query.go index 9409ac6..a14d1ab 100644 --- a/starter/ent/user_query.go +++ b/starter/ent/user_query.go @@ -5,14 +5,14 @@ package ent import ( "context" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/user" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // UserQuery is the builder for querying User entities. diff --git a/starter/ent/user_update.go b/starter/ent/user_update.go index 56a423f..c86919b 100644 --- a/starter/ent/user_update.go +++ b/starter/ent/user_update.go @@ -6,13 +6,13 @@ import ( "context" "errors" "fmt" - "git.gorlug.de/code/ersteller/starter/ent/predicate" - "git.gorlug.de/code/ersteller/starter/ent/user" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" + "git.gorlug.de/code/ersteller/starter/ent/predicate" + "git.gorlug.de/code/ersteller/starter/ent/user" ) // UserUpdate is the builder for updating User entities. @@ -48,16 +48,16 @@ func (_u *UserUpdate) SetNillableEmail(v *string) *UserUpdate { return _u } -// SetPassword sets the "password" field. -func (_u *UserUpdate) SetPassword(v string) *UserUpdate { - _u.mutation.SetPassword(v) +// SetPasswordHash sets the "password_hash" field. +func (_u *UserUpdate) SetPasswordHash(v string) *UserUpdate { + _u.mutation.SetPasswordHash(v) return _u } -// SetNillablePassword sets the "password" field if the given value is not nil. -func (_u *UserUpdate) SetNillablePassword(v *string) *UserUpdate { +// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil. +func (_u *UserUpdate) SetNillablePasswordHash(v *string) *UserUpdate { if v != nil { - _u.SetPassword(*v) + _u.SetPasswordHash(*v) } return _u } @@ -118,8 +118,8 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.Email(); ok { _spec.SetField(user.FieldEmail, field.TypeString, value) } - if value, ok := _u.mutation.Password(); ok { - _spec.SetField(user.FieldPassword, field.TypeString, value) + if value, ok := _u.mutation.PasswordHash(); ok { + _spec.SetField(user.FieldPasswordHash, field.TypeString, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { @@ -161,16 +161,16 @@ func (_u *UserUpdateOne) SetNillableEmail(v *string) *UserUpdateOne { return _u } -// SetPassword sets the "password" field. -func (_u *UserUpdateOne) SetPassword(v string) *UserUpdateOne { - _u.mutation.SetPassword(v) +// SetPasswordHash sets the "password_hash" field. +func (_u *UserUpdateOne) SetPasswordHash(v string) *UserUpdateOne { + _u.mutation.SetPasswordHash(v) return _u } -// SetNillablePassword sets the "password" field if the given value is not nil. -func (_u *UserUpdateOne) SetNillablePassword(v *string) *UserUpdateOne { +// SetNillablePasswordHash sets the "password_hash" field if the given value is not nil. +func (_u *UserUpdateOne) SetNillablePasswordHash(v *string) *UserUpdateOne { if v != nil { - _u.SetPassword(*v) + _u.SetPasswordHash(*v) } return _u } @@ -261,8 +261,8 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { if value, ok := _u.mutation.Email(); ok { _spec.SetField(user.FieldEmail, field.TypeString, value) } - if value, ok := _u.mutation.Password(); ok { - _spec.SetField(user.FieldPassword, field.TypeString, value) + if value, ok := _u.mutation.PasswordHash(); ok { + _spec.SetField(user.FieldPasswordHash, field.TypeString, value) } _node = &User{config: _u.config} _spec.Assign = _node.assignValues diff --git a/starter/go.mod b/starter/go.mod index 3798967..4711ecb 100644 --- a/starter/go.mod +++ b/starter/go.mod @@ -7,20 +7,22 @@ require ( git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 - golang.org/x/oauth2 v0.30.0 + golang.org/x/crypto v0.44.0 + golang.org/x/oauth2 v0.32.0 maragu.dev/gomponents v1.2.0 + maragu.dev/gomponents-htmx v0.6.1 ) require ( ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-openapi/inflect v0.19.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -36,16 +38,16 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.1.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect - maragu.dev/gomponents-htmx v0.6.1 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect ) diff --git a/starter/go.sum b/starter/go.sum index 01e37ff..44db347 100644 --- a/starter/go.sum +++ b/starter/go.sum @@ -1,7 +1,7 @@ ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc= ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4= entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U= git.gorlug.de/code/ersteller v0.0.0-20251115110439-18d05566e2fc h1:oin+Mm/Z9FaR0To40MDGrFsEdK7s7FDaC9vImeyvMdg= @@ -23,12 +23,12 @@ github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNP github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= @@ -71,15 +71,15 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -88,23 +88,23 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8 github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/starter/login/login.go b/starter/login/login.go index 8fbe7c1..19d6a07 100644 --- a/starter/login/login.go +++ b/starter/login/login.go @@ -1,7 +1,15 @@ package login import ( + "net/http" + . "git.gorlug.de/code/ersteller" + "git.gorlug.de/code/ersteller/authentication" + "git.gorlug.de/code/ersteller/starter/ent" + "git.gorlug.de/code/ersteller/starter/ent/user" + "github.com/gorilla/sessions" + "golang.org/x/crypto/bcrypt" + hx "maragu.dev/gomponents-htmx" . "maragu.dev/gomponents" . "maragu.dev/gomponents/html" @@ -9,34 +17,55 @@ import ( const LoginPath = "/login" const LoginPathDe = "/anmelden" +const LocalLoginPath = "/login/local" +const LocalLoginPathDe = "/anmelden/lokal" var loginTexts *LoginTexts type LoginTexts struct { - PageTitle I18nText - PageDescription I18nText - HeroTitle I18nText - HeroDescription I18nText - GoogleLoginBtn I18nText + PageTitle I18nText + PageDescription I18nText + HeroTitle I18nText + HeroDescription I18nText + GoogleLoginBtn I18nText + EmailLabel I18nText + PasswordLabel I18nText + LoginBtn I18nText + OrSeparator I18nText + InvalidCreds I18nText + SessionSaveError I18nText } type Page struct { - createPage CreateHtmxPageFunc - ViewRoute HtmxRoute + createPage CreateHtmxPageFunc + ViewRoute HtmxRoute + db *ent.Client + sessionStore *sessions.CookieStore + localLoginRoute HtmxRoute } -func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath) *Page { +func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath, + db *ent.Client, sessionStore *sessions.CookieStore) *Page { if loginTexts == nil { createLoginTexts() } page := &Page{ - createPage: createPage, + createPage: createPage, + db: db, + sessionStore: sessionStore, } page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{ En: LoginPath, De: LoginPathDe, }).SetActivePath(path) page.ViewRoute.Add(server) + + // Add POST route for local login + page.localLoginRoute = NewHtmxPostRoute(page.HandleLocalLogin, LanguagePaths{ + En: LocalLoginPath, + De: LocalLoginPathDe, + }) + page.localLoginRoute.Add(server) return page } @@ -62,6 +91,30 @@ func createLoginTexts() { En: "Sign in with Google", De: "Mit Google anmelden", }), + EmailLabel: NewI18nText(map[Language]string{ + En: "Email", + De: "E-Mail", + }), + PasswordLabel: NewI18nText(map[Language]string{ + En: "Password", + De: "Passwort", + }), + LoginBtn: NewI18nText(map[Language]string{ + En: "Sign in", + De: "Anmelden", + }), + OrSeparator: NewI18nText(map[Language]string{ + En: "OR", + De: "ODER", + }), + InvalidCreds: NewI18nText(map[Language]string{ + En: "Invalid email or password", + De: "Ungültige E-Mail oder Passwort", + }), + SessionSaveError: NewI18nText(map[Language]string{ + En: "Failed to save session", + De: "Sitzung konnte nicht gespeichert werden", + }), } } @@ -75,25 +128,95 @@ func (p *Page) getMetaData() PageWebsiteMetaData { } func (p *Page) View(c HtmxContext) { - content := LoginContent(c.GetLanguage()) + content := p.LoginContent(c.GetLanguage(), "") p.createPage(c, p.getMetaData(), content) } -func LoginContent(language Language) Group { - return []Node{ +func (p *Page) LoginContent(language Language, errorMsg string) Group { + nodes := []Node{ Div(Class("hero-section login-section"), H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))), P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))), - Div(Class("login-buttons"), - A( - Href("/login/google"), - Button( - Class("btn btn-primary google-login-btn"), - Type("button"), - Text(loginTexts.GoogleLoginBtn.FromLang(language)), - ), + ), + } + + if errorMsg != "" { + nodes = append(nodes, Div(Class("error-message"), Text(errorMsg))) + } + + nodes = append(nodes, + Form( + p.localLoginRoute.GetHtmx(language), + hx.Target("body"), + Class("login-form"), + Div(Class("form-group"), + Label(For("email"), Text(loginTexts.EmailLabel.FromLang(language))), + Input(Type("email"), ID("email"), Name("email"), Required(), Class("form-control")), + ), + Div(Class("form-group"), + Label(For("password"), Text(loginTexts.PasswordLabel.FromLang(language))), + Input(Type("password"), ID("password"), Name("password"), Required(), Class("form-control")), + ), + Button( + Type("submit"), + Class("btn btn-primary"), + Text(loginTexts.LoginBtn.FromLang(language)), + ), + ), + Div(Class("separator"), Text(loginTexts.OrSeparator.FromLang(language))), + Div(Class("login-buttons"), + A( + Href("/login/google"), + Button( + Class("btn btn-primary google-login-btn"), + Type("button"), + Text(loginTexts.GoogleLoginBtn.FromLang(language)), ), ), ), - } + ) + + return nodes +} + +func (p *Page) HandleLocalLogin(c HtmxContext) { + req := c.GetRequest() + if err := req.ParseForm(); err != nil { + content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage())) + p.createPage(c, p.getMetaData(), content) + return + } + + email := req.FormValue("email") + password := req.FormValue("password") + + // Find user by email + ctx := req.Context() + foundUser, err := p.db.User.Query().Where(user.EmailEQ(email)).Only(ctx) + if err != nil { + Error("could not find user ", email, "with error:", err) + content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage())) + p.createPage(c, p.getMetaData(), content) + return + } + + // Verify password + if err := bcrypt.CompareHashAndPassword([]byte(foundUser.PasswordHash), []byte(password)); err != nil { + Error("could not verify password for ", email, "with error:", err) + content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage())) + p.createPage(c, p.getMetaData(), content) + return + } + + // Set session + err = authentication.SaveEmailAndUserIdToSessionStore(c.GetRequest(), c.GetResponseWriter(), p.sessionStore, foundUser.Email, foundUser.ID) + if err != nil { + Error("could not save user id for ", email, "to session store:", err) + content := p.LoginContent(c.GetLanguage(), loginTexts.SessionSaveError.FromLang(c.GetLanguage())) + p.createPage(c, p.getMetaData(), content) + return + } + + // Redirect to home page + http.Redirect(c.GetResponseWriter(), req, "/", http.StatusSeeOther) } diff --git a/starter/routes/routing.go b/starter/routes/routing.go index 773a4fb..88a8137 100644 --- a/starter/routes/routing.go +++ b/starter/routes/routing.go @@ -1,6 +1,8 @@ package routes import ( + "net/http" + . "git.gorlug.de/code/ersteller" "git.gorlug.de/code/ersteller/authentication" google_http "git.gorlug.de/code/ersteller/authentication/google/http" @@ -13,7 +15,6 @@ import ( "git.gorlug.de/code/ersteller/starter/login" "git.gorlug.de/code/ersteller/starter/page" "git.gorlug.de/code/ersteller/starter/todos" - "net/http" "github.com/gorilla/sessions" . "maragu.dev/gomponents" @@ -97,7 +98,7 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler { En: "Login", De: "Anmelden", }, loginPaths) - _ = login.NewPage(createPageFunc, server, &loginActivePath) + _ = login.NewPage(createPageFunc, server, &loginActivePath, db, sessionStore) serverWithMiddleWare := UseMiddleware(server, LoggingMiddleware, MakeGzipHandler, authentication.Middleware(sessionStore, diff --git a/starter/static/styles.css b/starter/static/styles.css index 3cc991a..6e2388d 100644 --- a/starter/static/styles.css +++ b/starter/static/styles.css @@ -461,6 +461,7 @@ a.disabled { box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.4); } + /* Login section specific styles */ .login-section { max-width: 400px; @@ -468,12 +469,34 @@ a.disabled { padding: 40px 30px; } -.login-buttons { +.login-form { margin-top: 30px; display: flex; flex-direction: column; - align-items: center; - gap: 16px; + gap: 20px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + font-weight: 600; + margin-bottom: 8px; + color: #333; +} + +.error-message { + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); + color: white; + padding: 12px 20px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + font-weight: 500; + box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3); + animation: slideIn 0.3s ease; } /* Mobile responsive adjustments for login button */ @@ -488,3 +511,4 @@ a.disabled { padding: 30px 20px; } } +