Skip to content

Commit b298f40

Browse files
committed
wip
1 parent 4256af3 commit b298f40

File tree

8 files changed

+52
-95
lines changed

8 files changed

+52
-95
lines changed

api/services/auth.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser
101101
return nil, lockout, "", NewErrAuthUnathorized(nil)
102102
}
103103

104-
if !hash.CompareWith(req.Password, user.Password) {
104+
if !hash.CompareWith(req.Password, user.PasswordDigest) {
105105
lockout, _, err := s.cache.StoreLoginAttempt(ctx, sourceIP, user.ID)
106106
if err != nil {
107107
log.WithError(err).
@@ -158,16 +158,14 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser
158158
return nil, 0, "", NewErrTokenSigned(err)
159159
}
160160

161-
// Updates last_login and the hash algorithm to bcrypt if still using SHA256
162-
changes := &models.UserChanges{LastLogin: clock.Now(), PreferredNamespace: &tenantID}
163-
if !strings.HasPrefix(user.Password, "$") {
164-
if neo, _ := models.HashUserPassword(req.Password); neo.Hash != "" {
165-
changes.Password = neo.Hash
161+
user.LastLogin = clock.Now()
162+
if !strings.HasPrefix(user.PasswordDigest, "$") {
163+
if digest, _ := hash.Do(req.Password); digest != "" {
164+
user.PasswordDigest = digest
166165
}
167166
}
168167

169-
// TODO: evaluate make this update in a go routine.
170-
if err := s.store.UserUpdate(ctx, user.ID, changes); err != nil {
168+
if err := s.store.Save(ctx, user); err != nil {
171169
return nil, 0, "", NewErrUserUpdate(user, err)
172170
}
173171

@@ -184,12 +182,12 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser
184182
User: user.Username,
185183
Name: user.Name,
186184
Email: user.Email,
187-
RecoveryEmail: user.RecoveryEmail,
185+
RecoveryEmail: user.Preferences.SecurityEmail,
188186
MFA: user.MFA.Enabled,
189187
Tenant: tenantID,
190188
Role: role,
191189
Token: token,
192-
MaxNamespaces: user.MaxNamespaces,
190+
MaxNamespaces: user.Preferences.MaxNamespaces,
193191
}
194192

195193
return res, 0, "", nil

api/services/namespace.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ func (s *service) CreateNamespace(ctx context.Context, req *requests.NamespaceCr
3232

3333
// When MaxNamespaces is less than zero, it means that the user has no limit
3434
// of namespaces. If the value is zero, it means he has no right to create a new namespace
35-
if user.MaxNamespaces == 0 {
36-
return nil, NewErrNamespaceCreationIsForbidden(user.MaxNamespaces, nil)
37-
} else if user.MaxNamespaces > 0 {
35+
if user.Preferences.MaxNamespaces == 0 {
36+
return nil, NewErrNamespaceCreationIsForbidden(user.Preferences.MaxNamespaces, nil)
37+
} else if user.Preferences.MaxNamespaces > 0 {
3838
info, err := s.store.UserGetInfo(ctx, req.UserID)
3939
switch {
4040
case err != nil:
4141
return nil, err
42-
case len(info.OwnedNamespaces) >= user.MaxNamespaces:
43-
return nil, NewErrNamespaceLimitReached(user.MaxNamespaces, nil)
42+
case len(info.OwnedNamespaces) >= user.Preferences.MaxNamespaces:
43+
return nil, NewErrNamespaceLimitReached(user.Preferences.MaxNamespaces, nil)
4444
}
4545
}
4646

api/store/mongo/store.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ func NewStore(ctx context.Context, uri string, cache cache.Cache, opts ...option
6969

7070
return store, nil
7171
}
72+
73+
func (s *Store) Save(_ context.Context, _ any) error {
74+
return nil
75+
}

api/store/pg/pg.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,8 @@ func New(ctx context.Context, uri string, opts ...options.Option) (store.Store,
5151

5252
return pg, nil
5353
}
54+
55+
func (pg *pg) Save(ctx context.Context, model any) error {
56+
_, err := pg.driver.NewUpdate().Model(model).WherePK().Exec(ctx)
57+
return fromSqlError(err)
58+
}

api/store/store.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package store
22

3+
import "context"
4+
35
//go:generate mockery --name Store --filename store.go
46
type Store interface {
57
TagsStore
@@ -17,4 +19,6 @@ type Store interface {
1719
SystemStore
1820

1921
Options() QueryOptions
22+
23+
Save(context.Context, any) error
2024
}

cli/services/users.go

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/shellhub-io/shellhub/cli/pkg/inputs"
9+
"github.com/shellhub-io/shellhub/pkg/hash"
910
"github.com/shellhub-io/shellhub/pkg/models"
1011
"golang.org/x/text/cases"
1112
"golang.org/x/text/language"
@@ -30,23 +31,23 @@ func (s *service) UserCreate(ctx context.Context, input *inputs.UserCreate) (*mo
3031
}
3132
}
3233

33-
password, err := models.HashUserPassword(input.Password)
34+
passwordDigest, err := hash.Do(input.Password)
3435
if err != nil {
3536
return nil, ErrUserPasswordInvalid
3637
}
3738

3839
user := &models.User{
39-
Origin: models.UserOriginLocal,
40-
ExternalID: "",
41-
Status: models.UserStatusConfirmed,
42-
Name: cases.Title(language.AmericanEnglish).String(strings.ToLower(input.Username)),
43-
Email: strings.ToLower(input.Email),
44-
Username: strings.ToLower(input.Username),
45-
Password: password.Hash,
40+
Origin: models.UserOriginLocal,
41+
ExternalID: "",
42+
Status: models.UserStatusConfirmed,
43+
Name: cases.Title(language.AmericanEnglish).String(strings.ToLower(input.Username)),
44+
Email: strings.ToLower(input.Email),
45+
Username: strings.ToLower(input.Username),
46+
PasswordDigest: passwordDigest,
4647
Preferences: models.UserPreferences{
4748
PreferredNamespace: "",
4849
AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal},
49-
RecoveryEmail: "",
50+
SecurityEmail: "",
5051
MaxNamespaces: -1,
5152
EmailMarketing: false,
5253
},
@@ -105,17 +106,13 @@ func (s *service) UserUpdate(ctx context.Context, input *inputs.UserUpdate) erro
105106
return ErrUserNotFound
106107
}
107108

108-
password, err := models.HashUserPassword(input.Password)
109+
passwordDigest, err := hash.Do(input.Password)
109110
if err != nil {
110111
return ErrUserPasswordInvalid
111112
}
112113

113-
// TODO: validate this at cmd layer
114-
if ok, err := s.validator.Struct(password); !ok || err != nil {
115-
return ErrUserPasswordInvalid
116-
}
117-
118-
if err := s.store.UserUpdate(ctx, user.ID, &models.UserChanges{Password: password.Hash}); err != nil {
114+
user.PasswordDigest = passwordDigest
115+
if err := s.store.Save(ctx, user); err != nil {
119116
return ErrFailedUpdateUser
120117
}
121118

pkg/models/user.go

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package models
33
import (
44
"time"
55

6-
"github.com/shellhub-io/shellhub/pkg/hash"
76
"github.com/shellhub-io/shellhub/pkg/validator"
87
)
98

@@ -74,35 +73,21 @@ type User struct {
7473

7574
Status UserStatus `json:"status" bson:"status" bun:"status"`
7675

77-
Name string `json:"name" validate:"required,name" bun:"name"`
78-
Username string `json:"username" bson:"username" validate:"required,username" bun:"username"`
79-
Email string `json:"email" bson:"email" validate:"required,email" bun:"email"`
80-
Password string `json:"-" bson:",inline" bun:"password_digest"`
76+
Name string `json:"name" validate:"required,name" bun:"name"`
77+
Username string `json:"username" bson:"username" validate:"required,username" bun:"username"`
78+
Email string `json:"email" bson:"email" validate:"required,email" bun:"email"`
79+
PasswordDigest string `json:"-" bson:",inline" bun:"password_digest"`
8180

8281
Preferences UserPreferences `json:"preferences" bson:"preferences" bun:"embed:"`
83-
84-
///
85-
///
86-
///
87-
///
88-
///
89-
///
90-
91-
MFA UserMFA `json:"mfa" bson:"mfa" bun:"-"`
92-
RecoveryEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email" bun:"-"`
93-
MaxNamespaces int `json:"max_namespaces" bson:"max_namespaces" bun:"-"`
94-
EmailMarketing bool `json:"email_marketing" bson:"email_marketing" bun:"-"`
82+
MFA UserMFA `json:"mfa" bson:"mfa" bun:"-"`
9583
}
9684

97-
type UserData struct {
98-
Name string `json:"name" validate:"required,name"`
99-
Username string `json:"username" bson:"username" validate:"required,username"`
100-
Email string `json:"email" bson:"email" validate:"required,email"`
101-
// RecoveryEmail is a custom, non-unique email address that a user can use to recover their account
102-
// when they lose access to all other methods. It must never be equal to [UserData.Email].
103-
//
104-
// NOTE: Recovery email is available as a cloud-only feature and must be ignored in community.
105-
RecoveryEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email"`
85+
type UserPreferences struct {
86+
PreferredNamespace string `json:"-" bson:"preferred_namespace" bun:"preferred_namespace_id,nullzero"`
87+
AuthMethods []UserAuthMethod `json:"auth_methods" bson:"auth_methods" bun:"auth_methods,array"`
88+
SecurityEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email" bun:"security_email,nullzero"`
89+
MaxNamespaces int `json:"max_namespaces" bson:"max_namespaces" bun:"namespace_ownership_limit"`
90+
EmailMarketing bool `json:"email_marketing" bson:"email_marketing" bun:"email_marketing"`
10691
}
10792

10893
// UserMFA represents the attributes related to MFA for a user.
@@ -115,43 +100,6 @@ type UserMFA struct {
115100
RecoveryCodes []string `json:"-" bson:"recovery_codes"`
116101
}
117102

118-
type UserPreferences struct {
119-
PreferredNamespace string `json:"-" bson:"preferred_namespace" bun:"preferred_namespace_id,nullzero"`
120-
AuthMethods []UserAuthMethod `json:"auth_methods" bson:"auth_methods" bun:"auth_methods,array"`
121-
RecoveryEmail string `json:"recovery_email" bson:"recovery_email" validate:"omitempty,email" bun:"security_email,nullzero"`
122-
MaxNamespaces int `json:"max_namespaces" bson:"max_namespaces" bun:"namespace_ownership_limit"`
123-
EmailMarketing bool `json:"email_marketing" bson:"email_marketing" bun:"email_marketing"`
124-
}
125-
126-
type UserPassword struct {
127-
// Plain contains the plain text password.
128-
Plain string `json:"password" bson:"-" validate:"required,password"`
129-
// Hash contains the hashed pasword from plain text.
130-
Hash string `json:"-" bson:"password"`
131-
}
132-
133-
// HashUserPassword receives a plain password and hash it, returning
134-
// a [UserPassword].
135-
func HashUserPassword(plain string) (UserPassword, error) {
136-
p := UserPassword{
137-
Plain: plain,
138-
}
139-
140-
var err error
141-
p.Hash, err = hash.Do(p.Plain)
142-
143-
return p, err
144-
}
145-
146-
// Compare reports whether a plain password matches with hash.
147-
//
148-
// For compatibility purposes, it can compare using both SHA256 and bcrypt algorithms.
149-
// Hashes starting with "$" are assumed to be a bcrypt hash; otherwise, they are treated as
150-
// SHA256 hashes.
151-
func (p *UserPassword) Compare(plain string) bool {
152-
return hash.CompareWith(plain, p.Hash)
153-
}
154-
155103
// UserAuthIdentifier is an username or email used to authenticate.
156104
type UserAuthIdentifier string
157105

pkg/uuid/uuid.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ type goUUID struct{}
2626

2727
// This function is responsible for generating UUID v4 of the google package.
2828
func (g *goUUID) Generate() string {
29-
return uuid.NewString()
29+
u, _ := uuid.NewV7()
30+
return u.String()
3031
}

0 commit comments

Comments
 (0)