|
|
|
|
@ -10,6 +10,13 @@ import (
|
|
|
|
|
"golang.org/x/crypto/argon2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type authAccount struct {
|
|
|
|
|
*Account
|
|
|
|
|
*Argon2Credential
|
|
|
|
|
*Argon2Configuration
|
|
|
|
|
IsCurrentConf bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Authenticate authenticates userName and password match for userName in
|
|
|
|
|
// authMethodId. The account for the userName is returned if authentication
|
|
|
|
|
// is successful. Returns nil if authentication fails.
|
|
|
|
|
@ -32,14 +39,121 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, user
|
|
|
|
|
return nil, fmt.Errorf("password authenticate: no password: %w", db.ErrInvalidParameter)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AuthAccount struct {
|
|
|
|
|
*Account
|
|
|
|
|
*Argon2Credential
|
|
|
|
|
*Argon2Configuration
|
|
|
|
|
IsCurrentConf bool
|
|
|
|
|
acct, err := r.authenticate(ctx, authMethodId, userName, password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("password authenticate: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if acct == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !acct.IsCurrentConf {
|
|
|
|
|
cc, err := r.currentConfig(ctx, authMethodId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("password authenticate: retrieve current password configuration: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cred, err := newArgon2Credential(acct.PublicId, password, cc.argon2())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("password authenticate: update credential: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var accts []AuthAccount
|
|
|
|
|
// do not change the Credential Id
|
|
|
|
|
cred.PrivateId = acct.CredentialId
|
|
|
|
|
if err := cred.encrypt(ctx, r.wrapper); err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("password authenticate: update credential: encrypt: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fields := []string{"CtSalt", "DerivedKey", "PasswordConfId"}
|
|
|
|
|
metadata := cred.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
|
|
|
|
|
|
|
|
|
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
|
|
|
func(_ db.Reader, w db.Writer) error {
|
|
|
|
|
rowsUpdated, err := w.Update(ctx, cred, fields, nil, db.WithOplog(r.wrapper, metadata))
|
|
|
|
|
if err == nil && rowsUpdated > 1 {
|
|
|
|
|
return db.ErrMultipleRecords
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("password authenticate: update credential: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return acct.Account, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChangePassword updates the password for userName in authMethodId to new
|
|
|
|
|
// if old equals the stored password. The account for the userName is
|
|
|
|
|
// returned with a new CredentialId if password is successfully changed.
|
|
|
|
|
//
|
|
|
|
|
// Returns nil if old does not match the stored password for userName.
|
|
|
|
|
// Returns ErrPasswordsEqual if old and new are equal.
|
|
|
|
|
func (r *Repository) ChangePassword(ctx context.Context, authMethodId string, userName string, old, new string) (*Account, error) {
|
|
|
|
|
if authMethodId == "" {
|
|
|
|
|
return nil, fmt.Errorf("change password: no authMethodId: %w", db.ErrInvalidParameter)
|
|
|
|
|
}
|
|
|
|
|
if userName == "" {
|
|
|
|
|
return nil, fmt.Errorf("change password: no userName: %w", db.ErrInvalidParameter)
|
|
|
|
|
}
|
|
|
|
|
if old == "" {
|
|
|
|
|
return nil, fmt.Errorf("change password: no old password: %w", db.ErrInvalidParameter)
|
|
|
|
|
}
|
|
|
|
|
if new == "" {
|
|
|
|
|
return nil, fmt.Errorf("change password: no new password: %w", db.ErrInvalidParameter)
|
|
|
|
|
}
|
|
|
|
|
if old == new {
|
|
|
|
|
return nil, fmt.Errorf("change password: %w", ErrPasswordsEqual)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acct, err := r.authenticate(ctx, authMethodId, userName, old)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("change password: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if acct == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cc, err := r.currentConfig(ctx, authMethodId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("change password: retrieve current password configuration: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if cc.MinPasswordLength > len(new) {
|
|
|
|
|
return nil, fmt.Errorf("change password: %w", ErrTooShort)
|
|
|
|
|
}
|
|
|
|
|
newCred, err := newArgon2Credential(acct.PublicId, new, cc.argon2())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("change password: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := newCred.encrypt(ctx, r.wrapper); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("change password: encrypt: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oldCred := acct.Argon2Credential
|
|
|
|
|
|
|
|
|
|
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
|
|
|
func(_ db.Reader, w db.Writer) error {
|
|
|
|
|
rowsDeleted, err := w.Delete(ctx, oldCred, db.WithOplog(r.wrapper, oldCred.oplog(oplog.OpType_OP_TYPE_DELETE)))
|
|
|
|
|
if err == nil && rowsDeleted > 1 {
|
|
|
|
|
return db.ErrMultipleRecords
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return w.Create(ctx, newCred, db.WithOplog(r.wrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE)))
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("change password: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// change the Credential Id
|
|
|
|
|
acct.Account.CredentialId = newCred.PrivateId
|
|
|
|
|
return acct.Account, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Repository) authenticate(ctx context.Context, authMethodId string, userName string, password string) (*authAccount, error) {
|
|
|
|
|
var accts []authAccount
|
|
|
|
|
|
|
|
|
|
tx, err := r.reader.DB()
|
|
|
|
|
if err != nil {
|
|
|
|
|
@ -51,77 +165,32 @@ func (r *Repository) Authenticate(ctx context.Context, authMethodId string, user
|
|
|
|
|
}
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var aa AuthAccount
|
|
|
|
|
var aa authAccount
|
|
|
|
|
if err := r.reader.ScanRows(rows, &aa); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
accts = append(accts, aa)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var acct AuthAccount
|
|
|
|
|
var acct authAccount
|
|
|
|
|
switch {
|
|
|
|
|
case len(accts) == 0:
|
|
|
|
|
return nil, nil
|
|
|
|
|
case len(accts) > 1:
|
|
|
|
|
// this should never happen
|
|
|
|
|
return nil, fmt.Errorf("authenticate: multiple accounts returned for user name")
|
|
|
|
|
return nil, fmt.Errorf("multiple accounts returned for user name")
|
|
|
|
|
default:
|
|
|
|
|
acct = accts[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := acct.decrypt(ctx, r.wrapper); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("authenticate: credential: cannot decrypt value: %w", err)
|
|
|
|
|
return nil, fmt.Errorf("cannot decrypt credential: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inputKey := argon2.IDKey([]byte(password), acct.Salt, acct.Iterations, acct.Memory, uint8(acct.Threads), acct.KeyLength)
|
|
|
|
|
if subtle.ConstantTimeCompare(inputKey, acct.DerivedKey) == 0 {
|
|
|
|
|
// authentication failed, password does not match
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !acct.IsCurrentConf {
|
|
|
|
|
cc, err := r.currentConfig(ctx, authMethodId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("authenticate: retrieve current password configuration: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cred, err := newArgon2Credential(acct.PublicId, password, cc.argon2())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("authenticate: rehash current password: %w", err)
|
|
|
|
|
}
|
|
|
|
|
cred.PrivateId = acct.CredentialId
|
|
|
|
|
cred.PasswordMethodId = cc.PasswordMethodId
|
|
|
|
|
|
|
|
|
|
var newCred *Argon2Credential
|
|
|
|
|
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
|
|
|
|
func(_ db.Reader, w db.Writer) error {
|
|
|
|
|
newCred = cred.clone()
|
|
|
|
|
if err := newCred.encrypt(ctx, r.wrapper); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
rowsUpdated, err := w.Update(
|
|
|
|
|
ctx,
|
|
|
|
|
newCred,
|
|
|
|
|
[]string{"CtSalt", "DerivedKey", "PasswordConfId"},
|
|
|
|
|
nil,
|
|
|
|
|
db.WithOplog(r.wrapper, cred.oplog(oplog.OpType_OP_TYPE_UPDATE)),
|
|
|
|
|
)
|
|
|
|
|
if err == nil && rowsUpdated > 1 {
|
|
|
|
|
return db.ErrMultipleRecords
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return acct.Account, fmt.Errorf("authenticate: update rehashed password: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return acct.Account, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChangePassword updates the password for userName in authMethodId to new
|
|
|
|
|
// if old equals the stored password. The account for the userName is
|
|
|
|
|
// returned with a new CredentialId if password is successfully changed.
|
|
|
|
|
//
|
|
|
|
|
// Returns nil if old does not match the stored password for userName.
|
|
|
|
|
func (r *Repository) ChangePassword(ctx context.Context, authMethodId string, userName string, old, new string) (*Account, error) {
|
|
|
|
|
panic("not implemented")
|
|
|
|
|
return &acct, nil
|
|
|
|
|
}
|
|
|
|
|
|