diff options
Diffstat (limited to 'player-server/internal/service')
| -rw-r--r-- | player-server/internal/service/admin_test.go | 2 | ||||
| -rw-r--r-- | player-server/internal/service/service.go | 1 | ||||
| -rw-r--r-- | player-server/internal/service/user.go | 8 | ||||
| -rw-r--r-- | player-server/internal/service/user_test.go | 24 |
4 files changed, 29 insertions, 6 deletions
diff --git a/player-server/internal/service/admin_test.go b/player-server/internal/service/admin_test.go index 0e4d309..1a93356 100644 --- a/player-server/internal/service/admin_test.go +++ b/player-server/internal/service/admin_test.go @@ -200,7 +200,7 @@ func TestAdminService_CreateUser(t *testing.T) { } hasher := &fakeHasher{fixed: "hashed", err: tt.hashErr} svc := NewAdminService(store, newMockClock(), hasher, nil, "", ctx) - user, err := svc.CreateUser(ctx, "alice", "secret", false) + user, err := svc.CreateUser(ctx, "alice", "strongpass", false) if tt.wantErr { if err == nil { t.Fatal("expected error") diff --git a/player-server/internal/service/service.go b/player-server/internal/service/service.go index beeb5aa..9792f95 100644 --- a/player-server/internal/service/service.go +++ b/player-server/internal/service/service.go @@ -48,6 +48,7 @@ var ( ErrInvalidCredentials = &apiError{msg: "invalid credentials", status: http.StatusUnauthorized} ErrInvalidFeed = &apiError{msg: "invalid feed", status: http.StatusBadRequest} ErrCannotDeleteSelf = &apiError{msg: "cannot delete self", status: http.StatusBadRequest} + ErrWeakPassword = &apiError{msg: "password must be at least 8 characters", status: http.StatusBadRequest} // ErrShareExpired is handled directly by share handlers (not via // handleError); it stays a plain sentinel because no dispatch metadata diff --git a/player-server/internal/service/user.go b/player-server/internal/service/user.go index 01467ed..ba3e541 100644 --- a/player-server/internal/service/user.go +++ b/player-server/internal/service/user.go @@ -10,6 +10,9 @@ import ( "codeberg.org/snonux/player/internal/repository" ) +// minPasswordLen is the minimum acceptable password length for new accounts. +const minPasswordLen = 8 + // userAdminService handles user account management. type userAdminService struct { store repository.UserAdminServiceStore @@ -27,6 +30,11 @@ func (s *userAdminService) ListUsers(ctx context.Context) ([]model.User, error) } func (s *userAdminService) CreateUser(ctx context.Context, username, password string, isAdmin bool) (*model.User, error) { + // Reject blank or short passwords before hashing to prevent weak account creation. + if len(password) < minPasswordLen { + return nil, ErrWeakPassword + } + hash, err := s.hasher.Hash(password) if err != nil { return nil, fmt.Errorf("hash password: %w", err) diff --git a/player-server/internal/service/user_test.go b/player-server/internal/service/user_test.go index 1e0ceb3..b470119 100644 --- a/player-server/internal/service/user_test.go +++ b/player-server/internal/service/user_test.go @@ -30,23 +30,37 @@ func TestUserAdminService_CreateUser(t *testing.T) { tests := []struct { name string + password string hashErr error createErr error wantErr bool }{ { - name: "ok", + name: "ok", + password: "strongpass", // 10 chars, meets 8-char minimum }, { - name: "hash error", - hashErr: errors.New("boom"), - wantErr: true, + name: "hash error", + password: "strongpass", + hashErr: errors.New("boom"), + wantErr: true, }, { name: "create error", + password: "strongpass", createErr: errors.New("boom"), wantErr: true, }, + { + name: "empty password rejected", + password: "", + wantErr: true, + }, + { + name: "short password rejected", + password: "short", + wantErr: true, + }, } for _, tt := range tests { @@ -60,7 +74,7 @@ func TestUserAdminService_CreateUser(t *testing.T) { } hasher := &fakeUserHasher{fixed: "hashed", err: tt.hashErr} svc := NewUserAdminService(store, clock.RealClock{}, hasher) - user, err := svc.CreateUser(ctx, "alice", "secret", false) + user, err := svc.CreateUser(ctx, "alice", tt.password, false) if tt.wantErr { if err == nil { t.Fatal("expected error") |
