Changes
5 changed files (+148/-8)
-
-
@@ -32,6 +32,10 @@ var shouldCreateInitAdminCreationPassword = flag.Bool("init-admin-creation-password", false, "Whether generate a password for initial admin user creation", ) var shouldCreateAlice = flag.Bool( "create-alice", false, "Create an admin user \"Alice/Alice's password\"?", ) // charmbracelet/log uses 256-color for default styles. // In other words, they ignore common terminal emulator's palette and uses // semi-hard-coded color. Unsafe defaults.
-
@@ -98,5 +102,16 @@ logger.Fatal(err)} logger.Infof("Use this password to create initial user: %s", password) } if *shouldCreateAlice { logger.Debug("Creating admin user Alice...") id, err := setups.CreateAlice(db) if err != nil { logger.Fatal(err) } logger.Infof("Created admin user Alice. ID=%s", id) } }
-
-
setups/create_alice.go (new)
-
@@ -0,0 +1,50 @@// Copyright 2025 Shota FUJI // // This source code is licensed under Zero-Clause BSD License. // You can find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt // You may also obtain a copy of the Zero-Clause BSD License at // <https://opensource.org/license/0bsd> // // SPDX-License-Identifier: 0BSD package setups import ( "database/sql" "fmt" "github.com/google/uuid" "google.golang.org/protobuf/proto" "pocka.jp/x/event_sourcing_user_management_poc/gen/event" "pocka.jp/x/event_sourcing_user_management_poc/gen/model" ) // CreateAlice creates a new admin user named "Alice" with demo password of // "Alice's password". // CreateAlice returns an ID of the created user on success. func CreateAlice(db *sql.DB) (string, error) { id := uuid.New().String() passwordHash, salt := hashPassword("Alice's password") if err := insertEvents(db, []proto.Message{ &event.UserCreated{ Id: proto.String(id), DisplayName: proto.String("Alice"), Email: proto.String("alice@example.com"), }, &event.PasswordLoginConfigured{ PasswordHash: passwordHash, Salt: salt, }, &event.RoleAssigned{ UserId: proto.String(id), Role: model.Role.Enum(model.Role_ROLE_ADMIN), }, }); err != nil { return "", fmt.Errorf("Unable to create Alice: %s", err) } return id, nil }
-
-
setups/event.go (new)
-
@@ -0,0 +1,54 @@// Copyright 2025 Shota FUJI // // This source code is licensed under Zero-Clause BSD License. // You can find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt // You may also obtain a copy of the Zero-Clause BSD License at // <https://opensource.org/license/0bsd> // // SPDX-License-Identifier: 0BSD package setups import ( "context" "database/sql" "fmt" "reflect" "google.golang.org/protobuf/proto" ) func insertEvents(db *sql.DB, events []proto.Message) error { ctx := context.Background() tx, err := db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("Failed to begin transaction for events insertion: %s", err) } defer tx.Rollback() // TODO: Add event name column to events table stmt, err := tx.Prepare("INSERT OR ABORT INTO user_events (payload) VALUES (?)") if err != nil { return fmt.Errorf("Failed to prepare INSERT statement for event insertion: %s", err) } for _, event := range events { eventName := reflect.TypeOf(event).Name() data, err := proto.Marshal(event) if err != nil { return fmt.Errorf("Serializing of %s failed: %s", eventName, err) } if _, err := stmt.Exec(data); err != nil { return fmt.Errorf("Failed to INSERT %s: %s", eventName, err) } } if err = tx.Commit(); err != nil { return fmt.Errorf("Failed to commit transaction for events insertion: %s", err) } return nil }
-
-
setups/hash_password.go (new)
-
@@ -0,0 +1,28 @@// Copyright 2025 Shota FUJI // // This source code is licensed under Zero-Clause BSD License. // You can find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt // You may also obtain a copy of the Zero-Clause BSD License at // <https://opensource.org/license/0bsd> // // SPDX-License-Identifier: 0BSD package setups import ( "crypto/rand" "golang.org/x/crypto/argon2" ) // hashPassword returns hash of the password and salt used for the hash. func hashPassword(password string) ([]byte, []byte) { salt := make([]byte, 32) // rand.Read never returns an error. // https://pkg.go.dev/crypto/rand@go1.24.1#Read rand.Read(salt) // Parameters recommended in RFC (according to Go docs) return argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32), salt }
-
-
-
@@ -14,7 +14,6 @@ "crypto/rand""database/sql" "fmt" "golang.org/x/crypto/argon2" "google.golang.org/protobuf/proto" "pocka.jp/x/event_sourcing_user_management_poc/gen/event"
-
@@ -27,13 +26,7 @@ // inefficient in real-world use cases.func InitAdminCreationPassword(db *sql.DB) (string, error) { password := rand.Text() salt := make([]byte, 32) // rand.Read never returns an error. // https://pkg.go.dev/crypto/rand@go1.24.1#Read rand.Read(salt) // Parameters recommended in RFC (according to Go docs) passwordHash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32) passwordHash, salt := hashPassword(password) ev := &event.InitialAdminCreationPasswordCreated{ PasswordHash: passwordHash,
-