Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/api/integration_test/password_flow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestPasswordLoginFlow(t *testing.T) {
require.NoError(t, err)

passwordRepo := harness.EnsureUserPasswordRepo(t)
require.NoError(t, passwordRepo.Create(t.Context(), db, &domain.CreateUserPassword{
require.NoError(t, passwordRepo.Set(t.Context(), db, &domain.SetUserPassword{
ProjectID: project.ID,
UserID: userID,
EncodedHash: encodedHash,
Expand Down
4 changes: 2 additions & 2 deletions internal/bootstrap/users/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ func importFile(
return fmt.Errorf("create user: %w", err)
}

if err := passwordRepo.Create(ctx, pool, &domain.CreateUserPassword{
if err := passwordRepo.Set(ctx, pool, &domain.SetUserPassword{
ProjectID: doc.Header.ProjectID,
UserID: doc.Header.ID,
EncodedHash: pw.EncodedHash,
ChangeRequired: pw.ChangeRequired,
}); err != nil {
return fmt.Errorf("create password: %w", err)
return fmt.Errorf("set password: %w", err)
}

slog.Info("bootstrap user: loaded user", slog.String("path", path), slog.String("id", doc.Header.ID))
Expand Down
12 changes: 6 additions & 6 deletions internal/domain/flow_on_success_create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ type flowUserWriter interface {
Create(ctx context.Context, client database.QueryExecutor, user *CreateUser) error
}

type flowUserPasswordWriter interface {
Create(ctx context.Context, client database.QueryExecutor, password *CreateUserPassword) error
type flowUserPasswordSetter interface {
Set(ctx context.Context, client database.QueryExecutor, password *SetUserPassword) error
}

// FlowCreateUserHandler implements the `create_user` on_success:
// persist a new user from validated identifier + password fields.
type FlowCreateUserHandler struct {
ids idgen.Generator
users flowUserWriter
passwords flowUserPasswordWriter
passwords flowUserPasswordSetter
hasher FlowPasswordHasher
}

func NewFlowCreateUserHandler(ids idgen.Generator, users flowUserWriter, passwords flowUserPasswordWriter, hasher FlowPasswordHasher) *FlowCreateUserHandler {
func NewFlowCreateUserHandler(ids idgen.Generator, users flowUserWriter, passwords flowUserPasswordSetter, hasher FlowPasswordHasher) *FlowCreateUserHandler {
return &FlowCreateUserHandler{ids: ids, users: users, passwords: passwords, hasher: hasher}
}

Expand Down Expand Up @@ -88,12 +88,12 @@ func (h *FlowCreateUserHandler) Handle(ctx context.Context, client database.Quer
return FlowOnSuccessResult{}, fmt.Errorf("flow on_success create_user: insert user: %w", err)
}

if err := h.passwords.Create(ctx, client, &CreateUserPassword{
if err := h.passwords.Set(ctx, client, &SetUserPassword{
ProjectID: in.ProjectID,
UserID: userID,
EncodedHash: encodedHash,
}); err != nil {
return FlowOnSuccessResult{}, fmt.Errorf("flow on_success create_user: insert password: %w", err)
return FlowOnSuccessResult{}, fmt.Errorf("flow on_success create_user: set password: %w", err)
}

return FlowOnSuccessResult{UserID: userID}, nil
Expand Down
10 changes: 5 additions & 5 deletions internal/domain/flow_state_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ func (f *fakeUserRepo) Create(_ context.Context, _ database.QueryExecutor, user

// fakeUserPasswordRepo records the password rows create_user persists.
type fakeUserPasswordRepo struct {
created []*domain.CreateUserPassword
set []*domain.SetUserPassword
}

func (f *fakeUserPasswordRepo) Create(_ context.Context, _ database.QueryExecutor, pw *domain.CreateUserPassword) error {
f.created = append(f.created, pw)
func (f *fakeUserPasswordRepo) Set(_ context.Context, _ database.QueryExecutor, pw *domain.SetUserPassword) error {
f.set = append(f.set, pw)
return nil
}

Expand Down Expand Up @@ -322,8 +322,8 @@ func TestFlowStateMachine_Process_RegistrationHappyPath(t *testing.T) {
wantUserID := "user_01TEST"
assert.Equal(t, wantUserID, w.users.created[0].ID)

require.Len(t, w.pws.created, 1)
assert.Equal(t, "hashed:correct-horse-battery-staple", w.pws.created[0].EncodedHash)
require.Len(t, w.pws.set, 1)
assert.Equal(t, "hashed:correct-horse-battery-staple", w.pws.set[0].EncodedHash)

// create_user pins the user ID and registers them on the attempt so the
// terminal step can issue a handoff token and auto-sign-in the new user.
Expand Down
4 changes: 2 additions & 2 deletions internal/domain/user_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (u *UserPassword) Verify(password string, verifier crypto.HashVerifier) err
return nil
}

type CreateUserPassword struct {
type SetUserPassword struct {
ProjectID string
UserID string
EncodedHash string
Expand All @@ -59,7 +59,7 @@ type UserPasswordRepository interface {
GetByUserID(ctx context.Context, client database.QueryExecutor, projectID string, userID string) (*UserPassword, error)
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*UserPassword, error)
List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*UserPassword, error)
Create(ctx context.Context, client database.QueryExecutor, user *CreateUserPassword) error
Set(ctx context.Context, client database.QueryExecutor, user *SetUserPassword) error
Comment thread
adlerhurst marked this conversation as resolved.
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) error
DeleteByUserID(ctx context.Context, client database.QueryExecutor, projectID string, userID string) error
}
Expand Down
9 changes: 2 additions & 7 deletions internal/service/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,7 @@ func (o *SetPasswordUserAction) Prepare(_ context.Context, _ database.QueryExecu
}

func (o *SetPasswordUserAction) Apply(ctx context.Context, db database.QueryExecutor) error {
err := o.passwordRepo.DeleteByUserID(ctx, db, o.ProjectID, o.UserID)
if err != nil {
return domain.ErrInternal(err).WithMessage("failed to remove old password from database")
}

err = o.passwordRepo.Create(ctx, db, &domain.CreateUserPassword{
err := o.passwordRepo.Set(ctx, db, &domain.SetUserPassword{
ProjectID: o.ProjectID,
UserID: o.UserID,
EncodedHash: o.hash,
Expand All @@ -257,7 +252,7 @@ func (o *SetPasswordUserAction) Apply(ctx context.Context, db database.QueryExec
if _, ok := errors.AsType[*database.ForeignKeyError](err); ok {
return domain.ErrUserNotFound()
}
return domain.ErrInternal(err).WithMessage("failed to set initial password")
return domain.ErrInternal(err).WithMessage("failed to set password")
}
return nil
}
59 changes: 57 additions & 2 deletions internal/storage/database/repository/user_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestUserPasswordRepository_CRUD(t *testing.T) {
insertProjectTeamSchemaUser(t, tx, pid, tid, schemaURL, userID)

vid := "verif-1"
require.NoError(t, repo.Create(ctx, tx, &domain.CreateUserPassword{
require.NoError(t, repo.Set(ctx, tx, &domain.SetUserPassword{
ProjectID: pid,
UserID: userID,
EncodedHash: "argon2id$v=19$m=65536,t=3,p=4$fake",
Expand Down Expand Up @@ -80,7 +80,7 @@ func TestUserPasswordRepository_CRUD(t *testing.T) {
_, err = repo.Get(ctx, tx, database.WithCondition(repo.UniqueCondition(pid, userID)))
require.ErrorIs(t, err, new(database.NoRowFoundError))

require.NoError(t, repo.Create(ctx, tx, &domain.CreateUserPassword{
require.NoError(t, repo.Set(ctx, tx, &domain.SetUserPassword{
ProjectID: pid,
UserID: userID,
EncodedHash: "argon2id$v=19$m=65536,t=3,p=4$fake2",
Expand All @@ -95,6 +95,61 @@ func TestUserPasswordRepository_CRUD(t *testing.T) {
require.ErrorIs(t, err, new(database.NoRowFoundError))
}

func TestUserPasswordRepository_SetUpsert(t *testing.T) {
skipIfSpanner(t)
repo := repository.NewUserPasswordRepository()
tx, rollback := transactionForRollback(t)
defer rollback()
ctx := t.Context()

const (
pid = "proj-cred-pw-upsert"
tid = "team-cred-pw-upsert"
schemaURL = "https://schemas.test/cred-pw-upsert.json"
userID = "usr_pw_upsert"
)

insertProjectTeamSchemaUser(t, tx, pid, tid, schemaURL, userID)

require.NoError(t, repo.Set(ctx, tx, &domain.SetUserPassword{
ProjectID: pid,
UserID: userID,
EncodedHash: "argon2id$v=19$m=65536,t=3,p=4$initial",
ChangeRequired: true,
}))

got, err := repo.Get(ctx, tx, database.WithCondition(repo.UniqueCondition(pid, userID)))
require.NoError(t, err)
initialID := got.ID
require.Equal(t, "argon2id$v=19$m=65536,t=3,p=4$initial", got.EncodedHash)
require.True(t, got.ChangeRequired)

_, err = tx.Exec(ctx,
fmt.Sprintf(`UPDATE %s SET failed_attempts = 3, last_successful_check = NOW() WHERE project_id = $1 AND user_id = $2`, dbTable("user_passwords")),
pid, userID,
)
require.NoError(t, err)

require.NoError(t, repo.Set(ctx, tx, &domain.SetUserPassword{
ProjectID: pid,
UserID: userID,
EncodedHash: "argon2id$v=19$m=65536,t=3,p=4$updated",
ChangeRequired: false,
}))

got2, err := repo.Get(ctx, tx, database.WithCondition(repo.UniqueCondition(pid, userID)))
require.NoError(t, err)
require.Equal(t, initialID, got2.ID)
require.Equal(t, "argon2id$v=19$m=65536,t=3,p=4$updated", got2.EncodedHash)
require.False(t, got2.ChangeRequired)
require.Zero(t, got2.FailedAttempts)
require.Nil(t, got2.LastSuccessfulCheck)

list, err := repo.List(ctx, tx, database.WithCondition(repo.ProjectIDCondition(pid)))
require.NoError(t, err)
require.Len(t, list, 1)
}

func TestUserTOTPRepository_CRUD(t *testing.T) {
skipIfSpanner(t)
repo := repository.NewUserTOTPRepository()
Expand Down
11 changes: 9 additions & 2 deletions internal/storage/database/repository/user_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (r *UserPasswordRepository) List(ctx context.Context, client database.Query
return out, nil
}

func (r *UserPasswordRepository) Create(ctx context.Context, client database.QueryExecutor, pw *domain.CreateUserPassword) error {
func (r *UserPasswordRepository) Set(ctx context.Context, client database.QueryExecutor, pw *domain.SetUserPassword) error {
builder := database.NewStatementBuilder("INSERT INTO ")
builder.WriteString(r.qualifiedTableName())
builder.WriteString(" (")
Expand All @@ -171,7 +171,14 @@ func (r *UserPasswordRepository) Create(ctx context.Context, client database.Que
}.WriteUnqualified(builder)
builder.WriteString(") VALUES (")
builder.WriteArgs(pw.ProjectID, pw.UserID, pw.EncodedHash, pw.ChangeRequired, pw.VerificationID)
builder.WriteString(")")
builder.WriteString(") ON CONFLICT (project_id, user_id) DO UPDATE SET")
builder.WriteString(" encoded_hash = EXCLUDED.encoded_hash,")
builder.WriteString(" change_required = EXCLUDED.change_required,")
builder.WriteString(" verification_id = EXCLUDED.verification_id,")
builder.WriteString(" changed_at = NOW(),")
builder.WriteString(" failed_attempts = 0,")
builder.WriteString(" last_successful_check = NULL,")
builder.WriteString(" updated_at = NOW()")
_, err := client.Exec(ctx, builder.String(), builder.Args()...)
return err
}
Expand Down
Loading