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