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