github.com/greenpau/go-authcrunch@v1.0.50/pkg/identity/database.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package identity
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/greenpau/go-authcrunch/pkg/errors"
    28  	"github.com/greenpau/go-authcrunch/pkg/requests"
    29  	"github.com/greenpau/go-authcrunch/pkg/util"
    30  	fileutil "github.com/greenpau/go-authcrunch/pkg/util/file"
    31  	"github.com/greenpau/versioned"
    32  )
    33  
    34  var (
    35  	app           *versioned.PackageManager
    36  	appVersion    string
    37  	gitBranch     string
    38  	gitCommit     string
    39  	buildUser     string
    40  	buildDate     string
    41  	defaultPolicy = Policy{
    42  		User: UserPolicy{
    43  			MinLength:            3,
    44  			MaxLength:            50,
    45  			AllowNonAlphaNumeric: false,
    46  			AllowUppercase:       false,
    47  		},
    48  		Password: PasswordPolicy{
    49  			KeepVersions:           10,
    50  			MinLength:              8,
    51  			MaxLength:              128,
    52  			RequireUppercase:       false,
    53  			RequireLowercase:       false,
    54  			RequireNumber:          false,
    55  			RequireNonAlphaNumeric: false,
    56  			BlockReuse:             false,
    57  			BlockPasswordChange:    false,
    58  		},
    59  	}
    60  )
    61  
    62  func init() {
    63  	app = versioned.NewPackageManager("authdb")
    64  	app.Description = "authdb"
    65  	app.Documentation = "https://github.com/greenpau/go-authcrunch"
    66  	app.SetVersion(appVersion, "1.0.50")
    67  	app.SetGitBranch(gitBranch, "main")
    68  	app.SetGitCommit(gitCommit, "v1.0.49-3-g5faadfc")
    69  	app.SetBuildUser(buildUser, "")
    70  	app.SetBuildDate(buildDate, "")
    71  }
    72  
    73  // Policy represents database usage policy.
    74  type Policy struct {
    75  	Password PasswordPolicy `json:"password,omitempty" xml:"password,omitempty" yaml:"password,omitempty"`
    76  	User     UserPolicy     `json:"user,omitempty" xml:"user,omitempty" yaml:"user,omitempty"`
    77  }
    78  
    79  // PasswordPolicy represents database password policy.
    80  type PasswordPolicy struct {
    81  	KeepVersions           int  `json:"keep_versions" xml:"keep_versions" yaml:"keep_versions"`
    82  	MinLength              int  `json:"min_length" xml:"min_length" yaml:"min_length"`
    83  	MaxLength              int  `json:"max_length" xml:"max_length" yaml:"max_length"`
    84  	RequireUppercase       bool `json:"require_uppercase" xml:"require_uppercase" yaml:"require_uppercase"`
    85  	RequireLowercase       bool `json:"require_lowercase" xml:"require_lowercase" yaml:"require_lowercase"`
    86  	RequireNumber          bool `json:"require_number" xml:"require_number" yaml:"require_number"`
    87  	RequireNonAlphaNumeric bool `json:"require_non_alpha_numeric" xml:"require_non_alpha_numeric" yaml:"require_non_alpha_numeric"`
    88  	BlockReuse             bool `json:"block_reuse" xml:"block_reuse" yaml:"block_reuse"`
    89  	BlockPasswordChange    bool `json:"block_password_change" xml:"block_password_change" yaml:"block_password_change"`
    90  }
    91  
    92  // UserPolicy represents database username policy
    93  type UserPolicy struct {
    94  	MinLength            int  `json:"min_length" xml:"min_length" yaml:"min_length"`
    95  	MaxLength            int  `json:"max_length" xml:"max_length" yaml:"max_length"`
    96  	AllowNonAlphaNumeric bool `json:"allow_non_alpha_numeric" xml:"allow_non_alpha_numeric" yaml:"allow_non_alpha_numeric"`
    97  	AllowUppercase       bool `json:"allow_uppercase" xml:"allow_uppercase" yaml:"allow_uppercase"`
    98  }
    99  
   100  // Database is user identity database.
   101  type Database struct {
   102  	mu              *sync.RWMutex
   103  	Version         string    `json:"version,omitempty" xml:"version,omitempty" yaml:"version,omitempty"`
   104  	Policy          Policy    `json:"policy,omitempty" xml:"policy,omitempty" yaml:"policy,omitempty"`
   105  	Revision        uint64    `json:"revision,omitempty" xml:"revision,omitempty" yaml:"revision,omitempty"`
   106  	LastModified    time.Time `json:"last_modified,omitempty" xml:"last_modified,omitempty" yaml:"last_modified,omitempty"`
   107  	Users           []*User   `json:"users,omitempty" xml:"users,omitempty" yaml:"users,omitempty"`
   108  	refEmailAddress map[string]*User
   109  	refUsername     map[string]*User
   110  	refID           map[string]*User
   111  	refAPIKey       map[string]*User
   112  	path            string
   113  }
   114  
   115  // NewDatabase return an instance of Database.
   116  func NewDatabase(fp string) (*Database, error) {
   117  	if fp == "/dev/null" {
   118  		return nil, errors.ErrNewDatabase.WithArgs(fp, "null path")
   119  	}
   120  
   121  	db := &Database{
   122  		mu:              &sync.RWMutex{},
   123  		path:            fp,
   124  		refUsername:     make(map[string]*User),
   125  		refID:           make(map[string]*User),
   126  		refEmailAddress: make(map[string]*User),
   127  		refAPIKey:       make(map[string]*User),
   128  	}
   129  	fileInfo, err := os.Stat(fp)
   130  	if err != nil {
   131  		if !os.IsNotExist(err) {
   132  			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   133  		}
   134  		if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil {
   135  			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   136  		}
   137  		db.Version = app.Version
   138  		db.enforceDefaultPolicy()
   139  		if err := db.commit(); err != nil {
   140  			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   141  		}
   142  	} else {
   143  		if fileInfo.IsDir() {
   144  			return nil, errors.ErrNewDatabase.WithArgs(fp, "path points to a directory")
   145  		}
   146  		b, err := fileutil.ReadFileBytes(fp)
   147  		if err != nil {
   148  			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   149  		}
   150  		if err := json.Unmarshal(b, db); err != nil {
   151  			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   152  		}
   153  		if changed := db.enforceDefaultPolicy(); changed {
   154  			if err := db.commit(); err != nil {
   155  				return nil, errors.ErrNewDatabase.WithArgs(fp, err)
   156  			}
   157  		}
   158  	}
   159  
   160  	// db.mu = &sync.RWMutex{}
   161  	// db.path = fp
   162  	db.Version = app.Version
   163  
   164  	for _, user := range db.Users {
   165  		if err := user.Valid(); err != nil {
   166  			return nil, errors.ErrNewDatabaseInvalidUser.WithArgs(user, err)
   167  		}
   168  		username := strings.ToLower(user.Username)
   169  		if _, exists := db.refUsername[username]; exists {
   170  			return nil, errors.ErrNewDatabaseDuplicateUser.WithArgs(user.Username, user)
   171  		}
   172  		if _, exists := db.refID[user.ID]; exists {
   173  			return nil, errors.ErrNewDatabaseDuplicateUserID.WithArgs(user.ID, user)
   174  		}
   175  		db.refUsername[username] = user
   176  		db.refID[user.ID] = user
   177  		for _, email := range user.EmailAddresses {
   178  			emailAddress := strings.ToLower(email.Address)
   179  			if _, exists := db.refEmailAddress[emailAddress]; exists {
   180  				return nil, errors.ErrNewDatabaseDuplicateEmail.WithArgs(emailAddress, user)
   181  			}
   182  			db.refEmailAddress[emailAddress] = user
   183  		}
   184  		for _, p := range user.Passwords {
   185  			if p.Algorithm == "" {
   186  				p.Algorithm = "bcrypt"
   187  			}
   188  		}
   189  		for _, apiKey := range user.APIKeys {
   190  			if _, exists := db.refAPIKey[apiKey.Prefix]; exists {
   191  				return nil, errors.ErrNewDatabaseDuplicateAPIKey.WithArgs(apiKey.Prefix, user)
   192  			}
   193  			db.refAPIKey[apiKey.Prefix] = user
   194  		}
   195  	}
   196  	return db, nil
   197  }
   198  
   199  func (db *Database) enforceDefaultPolicy() bool {
   200  	var changes int
   201  	if db.Policy.Password.MinLength == 0 {
   202  		db.Policy.Password.MinLength = defaultPolicy.Password.MinLength
   203  		changes++
   204  	}
   205  	if db.Policy.Password.MaxLength == 0 {
   206  		db.Policy.Password.MaxLength = defaultPolicy.Password.MaxLength
   207  		changes++
   208  	}
   209  	if db.Policy.Password.KeepVersions == 0 {
   210  		db.Policy.Password.KeepVersions = defaultPolicy.Password.KeepVersions
   211  		changes++
   212  	}
   213  	if db.Policy.User.MinLength == 0 {
   214  		db.Policy.User.MinLength = defaultPolicy.User.MinLength
   215  		changes++
   216  	}
   217  	if db.Policy.User.MaxLength == 0 {
   218  		db.Policy.User.MaxLength = defaultPolicy.User.MaxLength
   219  		changes++
   220  	}
   221  	if changes > 0 {
   222  		return true
   223  	}
   224  	return false
   225  }
   226  
   227  func (db *Database) checkPolicyCompliance(username, password string) error {
   228  	if err := db.checkUserPolicyCompliance(username); err != nil {
   229  		return err
   230  	}
   231  	if err := db.checkPasswordPolicyCompliance(password); err != nil {
   232  		return err
   233  	}
   234  	return nil
   235  }
   236  
   237  func (db *Database) checkUserPolicyCompliance(s string) error {
   238  	if len(s) > db.Policy.User.MaxLength || len(s) < db.Policy.User.MinLength {
   239  		return errors.ErrUserPolicyCompliance
   240  	}
   241  	return nil
   242  }
   243  
   244  func (db *Database) checkPasswordPolicyCompliance(s string) error {
   245  	if len(s) > db.Policy.Password.MaxLength || len(s) < db.Policy.Password.MinLength {
   246  		return errors.ErrPasswordPolicyCompliance
   247  	}
   248  	return nil
   249  }
   250  
   251  // GetPath returns the path  to Database.
   252  func (db *Database) GetPath() string {
   253  	return db.path
   254  }
   255  
   256  // AddUser adds user identity to the database.
   257  func (db *Database) AddUser(r *requests.Request) error {
   258  	db.mu.Lock()
   259  	defer db.mu.Unlock()
   260  
   261  	if err := db.checkPolicyCompliance(r.User.Username, r.User.Password); err != nil {
   262  		return errors.ErrAddUser.WithArgs(r.User.Username, err)
   263  	}
   264  
   265  	user, err := NewUserWithRoles(
   266  		r.User.Username, r.User.Password,
   267  		r.User.Email, r.User.FullName,
   268  		r.User.Roles,
   269  	)
   270  	if err != nil {
   271  		return errors.ErrAddUser.WithArgs(r.User.Username, err)
   272  	}
   273  	for i := 0; i < 10; i++ {
   274  		id := NewID()
   275  		if _, exists := db.refID[id]; !exists {
   276  			user.ID = id
   277  			break
   278  		}
   279  	}
   280  	username := strings.ToLower(user.Username)
   281  	if _, exists := db.refUsername[username]; exists {
   282  		return errors.ErrAddUser.WithArgs(username, "username already in use")
   283  	}
   284  
   285  	emailAddresses := []string{}
   286  	for _, email := range user.EmailAddresses {
   287  		emailAddress := strings.ToLower(email.Address)
   288  		if _, exists := db.refEmailAddress[emailAddress]; exists {
   289  			return errors.ErrAddUser.WithArgs(emailAddress, "email address already in use")
   290  		}
   291  		emailAddresses = append(emailAddresses, emailAddress)
   292  	}
   293  
   294  	if r.Query.ID != "" {
   295  		// Handle the case where registration ID is being provided with the request.
   296  		user.Registration = NewRegistration(r.Query.ID)
   297  	}
   298  
   299  	db.refUsername[username] = user
   300  	db.refID[user.ID] = user
   301  	for _, emailAddress := range emailAddresses {
   302  		db.refEmailAddress[emailAddress] = user
   303  	}
   304  	db.Users = append(db.Users, user)
   305  
   306  	if err := db.commit(); err != nil {
   307  		return errors.ErrAddUser.WithArgs(username, err)
   308  	}
   309  	return nil
   310  }
   311  
   312  // GetUsers return a list of user identities.
   313  func (db *Database) GetUsers(r *requests.Request) error {
   314  	db.mu.RLock()
   315  	defer db.mu.RUnlock()
   316  	_, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   317  	if err != nil {
   318  		return errors.ErrGetUsers.WithArgs(err)
   319  	}
   320  	bundle := NewUserMetadataBundle()
   321  	for _, user := range db.Users {
   322  		bundle.Add(user.GetMetadata())
   323  	}
   324  	r.Response.Payload = bundle
   325  	return nil
   326  }
   327  
   328  // GetUser return an instance of User.
   329  func (db *Database) GetUser(r *requests.Request) error {
   330  	db.mu.RLock()
   331  	defer db.mu.RUnlock()
   332  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   333  	if err != nil {
   334  		return errors.ErrGetUsers.WithArgs(err)
   335  	}
   336  	r.Response.Payload = user
   337  	return nil
   338  }
   339  
   340  // DeleteUser deletes a user by user id.
   341  func (db *Database) DeleteUser(r *requests.Request) error {
   342  	db.mu.Lock()
   343  	defer db.mu.Unlock()
   344  	// user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   345  	_, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   346  	if err != nil {
   347  		return errors.ErrDeleteUser.WithArgs(r.Query.ID, err)
   348  	}
   349  	return errors.ErrDeleteUser.WithArgs(r.Query.ID, "user delete operation is not supported")
   350  	// TODO: how do we delete a user ???
   351  
   352  	// if err := user.DeletePublicKey(r); err != nil {
   353  	//	return err
   354  	//}
   355  	/*
   356  		if err := db.commit(); err != nil {
   357  			return errors.ErrDeleteUser.WithArgs(r.Query.ID, err)
   358  		}
   359  		return nil
   360  	*/
   361  }
   362  
   363  // AuthenticateUser adds user identity to the database.
   364  func (db *Database) AuthenticateUser(r *requests.Request) error {
   365  	db.mu.RLock()
   366  	defer db.mu.RUnlock()
   367  	user, err := db.getUser(r.User.Username)
   368  	if err != nil {
   369  		r.Response.Code = 400
   370  		// Calculate password hash as the means to prevent user discovery.
   371  		NewPassword(r.User.Password)
   372  		return errors.ErrAuthFailed.WithArgs(err)
   373  	}
   374  
   375  	switch {
   376  	case r.User.Password != "":
   377  		if err := user.VerifyPassword(r.User.Password); err != nil {
   378  			r.Response.Code = 400
   379  			return errors.ErrAuthFailed.WithArgs(err)
   380  		}
   381  	case r.WebAuthn.Request != "":
   382  		if err := user.VerifyWebAuthnRequest(r); err != nil {
   383  			r.Response.Code = 400
   384  			return errors.ErrAuthFailed.WithArgs(err)
   385  		}
   386  	default:
   387  		r.Response.Code = 400
   388  		return errors.ErrAuthFailed.WithArgs("malformed auth request")
   389  	}
   390  
   391  	r.Response.Code = 200
   392  	return nil
   393  }
   394  
   395  // getUser return User by either email address or username.
   396  func (db *Database) getUser(s string) (*User, error) {
   397  	if strings.Contains(s, "@") {
   398  		return db.getUserByEmailAddress(s)
   399  	}
   400  	return db.getUserByUsername(s)
   401  }
   402  
   403  // getUserByID returns a user by id
   404  func (db *Database) getUserByID(s string) (*User, error) {
   405  	s = strings.ToLower(s)
   406  	user, exists := db.refID[s]
   407  	if exists && user != nil {
   408  		return user, nil
   409  	}
   410  	return nil, errors.ErrDatabaseUserNotFound
   411  }
   412  
   413  // getUserByUsername returns a user by username
   414  func (db *Database) getUserByUsername(s string) (*User, error) {
   415  	if len(s) < 2 {
   416  		return nil, errors.ErrDatabaseUserNotFound
   417  	}
   418  	s = strings.ToLower(s)
   419  	user, exists := db.refUsername[s]
   420  	if exists && user != nil {
   421  		return user, nil
   422  	}
   423  	return nil, errors.ErrDatabaseUserNotFound
   424  }
   425  
   426  // getUserByEmailAddress returns a liast of users associated with a specific email
   427  // address.
   428  func (db *Database) getUserByEmailAddress(s string) (*User, error) {
   429  	if len(s) < 6 {
   430  		return nil, errors.ErrDatabaseUserNotFound
   431  	}
   432  	s = strings.ToLower(s)
   433  	user, exists := db.refEmailAddress[s]
   434  	if exists && user != nil {
   435  		return user, nil
   436  	}
   437  	return nil, errors.ErrDatabaseUserNotFound
   438  }
   439  
   440  // GetUserCount returns user count.
   441  func (db *Database) GetUserCount() int {
   442  	db.mu.RLock()
   443  	defer db.mu.RUnlock()
   444  	return len(db.Users)
   445  }
   446  
   447  // GetAdminUserCount returns user count.
   448  func (db *Database) GetAdminUserCount() int {
   449  	db.mu.RLock()
   450  	defer db.mu.RUnlock()
   451  	var counter int
   452  	for _, user := range db.Users {
   453  		if user.HasAdminRights() {
   454  			counter++
   455  		}
   456  	}
   457  	return counter
   458  }
   459  
   460  // Save saves the database.
   461  func (db *Database) Save() error {
   462  	db.mu.Lock()
   463  	defer db.mu.Unlock()
   464  	return db.commit()
   465  }
   466  
   467  // Copy copies the database to another file.
   468  func (db *Database) Copy(fp string) error {
   469  	db.mu.Lock()
   470  	defer db.mu.Unlock()
   471  	path := db.path
   472  	db.path = fp
   473  	err := db.commit()
   474  	db.path = path
   475  	return err
   476  }
   477  
   478  // commit writes the database contents to a file.
   479  func (db *Database) commit() error {
   480  	db.Revision++
   481  	db.LastModified = time.Now().UTC()
   482  	data, err := json.MarshalIndent(db, "", "  ")
   483  	if err != nil {
   484  		return errors.ErrDatabaseCommit.WithArgs(db.path, err)
   485  	}
   486  	if err := ioutil.WriteFile(db.path, []byte(data), 0600); err != nil {
   487  		return errors.ErrDatabaseCommit.WithArgs(db.path, err)
   488  	}
   489  	return nil
   490  }
   491  
   492  func (db *Database) validateUserIdentity(username, email string) (*User, error) {
   493  	user1, err := db.getUserByUsername(username)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	user2, err := db.getUserByEmailAddress(email)
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  	if user1.ID != user2.ID {
   502  		return nil, errors.ErrDatabaseInvalidUser
   503  	}
   504  	return user1, nil
   505  }
   506  
   507  // AddPublicKey adds public key, e.g. GPG or SSH, for a user.
   508  func (db *Database) AddPublicKey(r *requests.Request) error {
   509  	db.mu.Lock()
   510  	defer db.mu.Unlock()
   511  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   512  	if err != nil {
   513  		return errors.ErrAddPublicKey.WithArgs(r.Key.Usage, err)
   514  	}
   515  	if err := user.AddPublicKey(r); err != nil {
   516  		return err
   517  	}
   518  	if err := db.commit(); err != nil {
   519  		return errors.ErrAddPublicKey.WithArgs(r.Key.Usage, err)
   520  	}
   521  	return nil
   522  }
   523  
   524  // GetPublicKeys returns a list of public keys associated with a user.
   525  func (db *Database) GetPublicKeys(r *requests.Request) error {
   526  	db.mu.RLock()
   527  	defer db.mu.RUnlock()
   528  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   529  	if err != nil {
   530  		return errors.ErrGetPublicKeys.WithArgs(r.Key.Usage, err)
   531  	}
   532  	bundle := NewPublicKeyBundle()
   533  	for _, k := range user.PublicKeys {
   534  		if k.Usage != r.Key.Usage {
   535  			continue
   536  		}
   537  		if k.Disabled {
   538  			continue
   539  		}
   540  		bundle.Add(k)
   541  	}
   542  	r.Response.Payload = bundle
   543  	return nil
   544  }
   545  
   546  // DeletePublicKey deletes a public key associated with a user by key id.
   547  func (db *Database) DeletePublicKey(r *requests.Request) error {
   548  	db.mu.Lock()
   549  	defer db.mu.Unlock()
   550  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   551  	if err != nil {
   552  		return errors.ErrDeletePublicKey.WithArgs(r.Key.ID, err)
   553  	}
   554  	if err := user.DeletePublicKey(r); err != nil {
   555  		return err
   556  	}
   557  	if err := db.commit(); err != nil {
   558  		return errors.ErrDeletePublicKey.WithArgs(r.Key.Usage, err)
   559  	}
   560  	return nil
   561  }
   562  
   563  // AddAPIKey adds API key for a user.
   564  func (db *Database) AddAPIKey(r *requests.Request) error {
   565  	db.mu.Lock()
   566  	defer db.mu.Unlock()
   567  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   568  	if err != nil {
   569  		return errors.ErrAddAPIKey.WithArgs(r.Key.Usage, err)
   570  	}
   571  	s := util.GetRandomString(72)
   572  	failCount := 0
   573  	for {
   574  		hk, err := NewPassword(s)
   575  		if err != nil {
   576  			if failCount > 10 {
   577  				return err
   578  			}
   579  			failCount++
   580  			continue
   581  		}
   582  		keyPrefix := string(s[:24])
   583  		if _, exists := db.refAPIKey[keyPrefix]; exists {
   584  			continue
   585  		}
   586  		r.Response.Payload = s
   587  		r.Key.Payload = hk.Hash
   588  		r.Key.Prefix = keyPrefix
   589  		if err := user.AddAPIKey(r); err != nil {
   590  			return err
   591  		}
   592  		db.refAPIKey[keyPrefix] = user
   593  		break
   594  	}
   595  
   596  	if err := db.commit(); err != nil {
   597  		return errors.ErrAddAPIKey.WithArgs(r.Key.Usage, err)
   598  	}
   599  	return nil
   600  }
   601  
   602  // DeleteAPIKey deletes an API key associated with a user by key id.
   603  func (db *Database) DeleteAPIKey(r *requests.Request) error {
   604  	db.mu.Lock()
   605  	defer db.mu.Unlock()
   606  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   607  	if err != nil {
   608  		return errors.ErrDeleteAPIKey.WithArgs(r.Key.ID, err)
   609  	}
   610  	if err := user.DeleteAPIKey(r); err != nil {
   611  		return err
   612  	}
   613  	delete(db.refAPIKey, r.Key.Prefix)
   614  	if err := db.commit(); err != nil {
   615  		return errors.ErrDeleteAPIKey.WithArgs(r.Key.Usage, err)
   616  	}
   617  	return nil
   618  }
   619  
   620  // GetAPIKeys returns a list of API keys associated with a user.
   621  func (db *Database) GetAPIKeys(r *requests.Request) error {
   622  	db.mu.RLock()
   623  	defer db.mu.RUnlock()
   624  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   625  	if err != nil {
   626  		return errors.ErrGetAPIKeys.WithArgs(r.Key.Usage, err)
   627  	}
   628  	bundle := NewAPIKeyBundle()
   629  	for _, k := range user.APIKeys {
   630  		if k.Usage != r.Key.Usage {
   631  			continue
   632  		}
   633  		if k.Disabled {
   634  			continue
   635  		}
   636  		bundle.Add(k)
   637  	}
   638  	r.Response.Payload = bundle
   639  	return nil
   640  }
   641  
   642  // ChangeUserPassword change user password.
   643  func (db *Database) ChangeUserPassword(r *requests.Request) error {
   644  	db.mu.Lock()
   645  	defer db.mu.Unlock()
   646  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   647  	if err != nil {
   648  		return errors.ErrChangeUserPassword.WithArgs(err)
   649  	}
   650  	if err := db.checkPasswordPolicyCompliance(r.User.Password); err != nil {
   651  		return errors.ErrChangeUserPassword.WithArgs(err)
   652  	}
   653  	if err := user.ChangePassword(r, db.Policy.Password.KeepVersions); err != nil {
   654  		return err
   655  	}
   656  	// if db.Policy.Password.KeepVersions
   657  	if err := db.commit(); err != nil {
   658  		return errors.ErrChangeUserPassword.WithArgs(err)
   659  	}
   660  	return nil
   661  }
   662  
   663  // UpdateUserPassword change user password.
   664  func (db *Database) UpdateUserPassword(r *requests.Request) error {
   665  	db.mu.Lock()
   666  	defer db.mu.Unlock()
   667  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   668  	if err != nil {
   669  		return errors.ErrUpdateUserPassword.WithArgs(err)
   670  	}
   671  	if err := db.checkPasswordPolicyCompliance(r.User.Password); err != nil {
   672  		return errors.ErrUpdateUserPassword.WithArgs(err)
   673  	}
   674  	if err := user.UpdatePassword(r, db.Policy.Password.KeepVersions); err != nil {
   675  		return err
   676  	}
   677  	if err := db.commit(); err != nil {
   678  		return errors.ErrUpdateUserPassword.WithArgs(err)
   679  	}
   680  	return nil
   681  }
   682  
   683  // IdentifyUser returns user identity and a list of challenges that should be
   684  // satisfied prior to successfully authenticating a user.
   685  func (db *Database) IdentifyUser(r *requests.Request) error {
   686  	db.mu.Lock()
   687  	defer db.mu.Unlock()
   688  	user, err := db.getUser(r.User.Username)
   689  	if err != nil {
   690  		r.User.Username = "nobody"
   691  		r.User.Email = "nobody@localhost"
   692  		r.User.Challenges = []string{"password"}
   693  		return nil
   694  	}
   695  	if r.Flags.Enabled {
   696  		user.GetFlags(r)
   697  	}
   698  	r.User.Username = user.Username
   699  	r.User.Email = user.GetMailClaim()
   700  	r.User.FullName = user.GetNameClaim()
   701  	r.User.Roles = user.GetRolesClaim()
   702  	r.User.Challenges = user.GetChallenges()
   703  	r.Response.Code = 200
   704  	return nil
   705  }
   706  
   707  // LookupAPIKey returns username and email associated with the provided API
   708  // key.
   709  func (db *Database) LookupAPIKey(r *requests.Request) error {
   710  	if r.Key.Payload == "" {
   711  		return errors.ErrLookupAPIKeyPayloadEmpty
   712  	}
   713  	if len(r.Key.Payload) < 72 {
   714  		return errors.ErrLookupAPIKeyMalformedPayload
   715  	}
   716  	r.Key.Prefix = string(r.Key.Payload[:24])
   717  	db.mu.Lock()
   718  	defer db.mu.Unlock()
   719  	user, exists := db.refAPIKey[r.Key.Prefix]
   720  	if !exists {
   721  		return errors.ErrLookupAPIKeyFailed
   722  	}
   723  	if err := user.LookupAPIKey(r); err != nil {
   724  		return err
   725  	}
   726  	r.User.Username = user.Username
   727  	r.User.Email = user.GetMailClaim()
   728  	r.Response.Code = 200
   729  	return nil
   730  }
   731  
   732  // AddMfaToken adds MFA token for a user.
   733  func (db *Database) AddMfaToken(r *requests.Request) error {
   734  	db.mu.Lock()
   735  	defer db.mu.Unlock()
   736  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   737  	if err != nil {
   738  		return errors.ErrAddMfaToken.WithArgs(err)
   739  	}
   740  	if err := user.AddMfaToken(r); err != nil {
   741  		return err
   742  	}
   743  	if err := db.commit(); err != nil {
   744  		return errors.ErrAddMfaToken.WithArgs(err)
   745  	}
   746  	return nil
   747  }
   748  
   749  // GetMfaTokens returns a list of MFA tokens associated with a user.
   750  func (db *Database) GetMfaTokens(r *requests.Request) error {
   751  	db.mu.RLock()
   752  	defer db.mu.RUnlock()
   753  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   754  	if err != nil {
   755  		return errors.ErrGetMfaTokens.WithArgs(err)
   756  	}
   757  	bundle := NewMfaTokenBundle()
   758  	for _, token := range user.MfaTokens {
   759  		if token.Disabled {
   760  			continue
   761  		}
   762  		bundle.Add(token)
   763  	}
   764  	r.Response.Payload = bundle
   765  	return nil
   766  }
   767  
   768  // DeleteMfaToken deletes MFA token associated with a user by token id.
   769  func (db *Database) DeleteMfaToken(r *requests.Request) error {
   770  	db.mu.Lock()
   771  	defer db.mu.Unlock()
   772  	user, err := db.validateUserIdentity(r.User.Username, r.User.Email)
   773  	if err != nil {
   774  		return errors.ErrDeleteMfaToken.WithArgs(r.MfaToken.ID, err)
   775  	}
   776  	if err := user.DeleteMfaToken(r); err != nil {
   777  		return err
   778  	}
   779  	if err := db.commit(); err != nil {
   780  		return errors.ErrDeleteMfaToken.WithArgs(r.MfaToken.ID, err)
   781  	}
   782  	return nil
   783  }
   784  
   785  // GetUsernamePolicySummary returns the summary of username policy.
   786  func (db *Database) GetUsernamePolicySummary() string {
   787  	var sb strings.Builder
   788  	var charRestrictions []string
   789  	sb.WriteString("A username should be")
   790  	sb.WriteString(fmt.Sprintf(" %d-%d character long string", db.Policy.User.MinLength, db.Policy.User.MaxLength))
   791  	if !db.Policy.User.AllowUppercase {
   792  		charRestrictions = append(charRestrictions, "lowercase")
   793  	}
   794  	if !db.Policy.User.AllowNonAlphaNumeric {
   795  		charRestrictions = append(charRestrictions, "alpha-numeric")
   796  	}
   797  	if len(charRestrictions) > 0 {
   798  		sb.WriteString(fmt.Sprintf(" with %s characters", strings.Join(charRestrictions, ", ")))
   799  	}
   800  	return sb.String()
   801  }
   802  
   803  // GetUsernamePolicyRegex returns regex for usernames.
   804  func (db *Database) GetUsernamePolicyRegex() string {
   805  	var startChars, allowedChars string
   806  	if !db.Policy.User.AllowUppercase {
   807  		startChars = "a-z"
   808  		allowedChars = "a-z0-9"
   809  	} else {
   810  		startChars = "a-zA-Z"
   811  		allowedChars = "a-zA-Z0-9"
   812  	}
   813  	if db.Policy.User.AllowNonAlphaNumeric {
   814  		allowedChars += "-_."
   815  	}
   816  	return fmt.Sprintf("^[%s][%s]{%d,%d}$", startChars, allowedChars, db.Policy.User.MinLength-1, db.Policy.User.MaxLength-1)
   817  }
   818  
   819  // GetPasswordPolicySummary returns the summary of password policy.
   820  func (db *Database) GetPasswordPolicySummary() string {
   821  	var sb strings.Builder
   822  	var charRestrictions []string
   823  	sb.WriteString("A password should be")
   824  	sb.WriteString(fmt.Sprintf(" %d-%d character long string", db.Policy.Password.MinLength, db.Policy.Password.MaxLength))
   825  	if db.Policy.Password.RequireUppercase {
   826  		charRestrictions = append(charRestrictions, "uppercase")
   827  	}
   828  	if db.Policy.Password.RequireLowercase {
   829  		charRestrictions = append(charRestrictions, "lowercase")
   830  	}
   831  	if db.Policy.Password.RequireNumber {
   832  		charRestrictions = append(charRestrictions, "numbers")
   833  	}
   834  	if db.Policy.Password.RequireNonAlphaNumeric {
   835  		charRestrictions = append(charRestrictions, "non alpha-numeric")
   836  	}
   837  
   838  	if len(charRestrictions) > 0 {
   839  		sb.WriteString(fmt.Sprintf(" with %s characters", strings.Join(charRestrictions, ", ")))
   840  	}
   841  	return sb.String()
   842  }
   843  
   844  // GetPasswordPolicyRegex returns regex for passwords.
   845  func (db *Database) GetPasswordPolicyRegex() string {
   846  	var allowedChars string
   847  	if db.Policy.Password.RequireUppercase {
   848  		allowedChars += "(?=.*[A-Z])"
   849  	}
   850  	if db.Policy.Password.RequireLowercase {
   851  		allowedChars += "(?=.*[a-z].*[a-z])"
   852  	}
   853  	if db.Policy.Password.RequireNumber {
   854  		allowedChars += "(?=.*[0-9].*[0-9])"
   855  	}
   856  	if db.Policy.Password.RequireNonAlphaNumeric {
   857  		allowedChars += "(?=.*[~!@#$&*])"
   858  	}
   859  
   860  	return fmt.Sprintf("^%s.{%d,%d}$", allowedChars, db.Policy.Password.MinLength, db.Policy.Password.MaxLength)
   861  
   862  }
   863  
   864  // UserExists checks whether user exists.
   865  func (db *Database) UserExists(username, emailAddress string) (bool, error) {
   866  	username = strings.ToLower(username)
   867  	emailAddress = strings.ToLower(emailAddress)
   868  	user1, _ := db.refUsername[username]
   869  	user2, _ := db.refEmailAddress[emailAddress]
   870  	switch {
   871  	case user1 == nil && user2 == nil:
   872  		return false, nil
   873  	case user1 == nil:
   874  		return false, fmt.Errorf("email is registered to a user, while username not found")
   875  	case user2 == nil:
   876  		return false, fmt.Errorf("username is registered to a user, while email not found")
   877  	}
   878  	if user1.ID != user2.ID {
   879  		return false, fmt.Errorf("username and email address belong to two different users")
   880  	}
   881  	return true, nil
   882  }