github.com/go-kivik/kivik/v4@v4.3.2/x/server/auth/userstore.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package auth 14 15 import ( 16 "context" 17 "crypto/rand" 18 "math/big" 19 "net/http" 20 "sync" 21 22 internal "github.com/go-kivik/kivik/v4/int/errors" 23 ) 24 25 // A UserStore provides an AuthHandler with access to a user store for. 26 type UserStore interface { 27 // Validate returns a user context object if the credentials are valid. An 28 // error must be returned otherwise. A Not-Found error will continue to the 29 // next user store, while any other error will terminate the auth process. 30 Validate(ctx context.Context, username, password string) (user *UserContext, err error) 31 // UserCtx returns a user context object if the user exists. It is used by 32 // AuthHandlers that don't validate the password (e.g. Cookie auth). If the 33 // user does not exist, a Not-Found error will be returned. 34 UserCtx(ctx context.Context, username string) (user *UserContext, err error) 35 } 36 37 // MemoryUserStore is a simple in-memory user store. 38 type MemoryUserStore struct { 39 users sync.Map 40 } 41 42 var _ UserStore = (*MemoryUserStore)(nil) 43 44 type memoryUser struct { 45 Salt string 46 Password string 47 Roles []string 48 } 49 50 // NewMemoryUserStore returns a new MemoryUserStore. 51 func NewMemoryUserStore() *MemoryUserStore { 52 return &MemoryUserStore{} 53 } 54 55 // AddUser adds a user to the store. It returns an error if the user already 56 // exists. 57 func (s *MemoryUserStore) AddUser(username, password string, roles []string) error { 58 salt, err := generateSalt() 59 if err != nil { 60 return err 61 } 62 _, loaded := s.users.LoadOrStore(username, &memoryUser{ 63 Salt: salt, 64 Password: password, 65 Roles: roles, 66 }) 67 if loaded { 68 return &internal.Error{Status: http.StatusConflict, Message: "User already exists"} 69 } 70 return nil 71 } 72 73 func generateSalt() (string, error) { 74 const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 75 const n = 16 76 ret := make([]byte, n) 77 for i := 0; i < n; i++ { 78 num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) 79 if err != nil { 80 return "", err 81 } 82 ret[i] = letters[num.Int64()] 83 } 84 85 return string(ret), nil 86 } 87 88 // DeleteUser deletes a user from the store. 89 func (s *MemoryUserStore) DeleteUser(username string) { 90 s.users.Delete(username) 91 } 92 93 var ( 94 errNotFound = &internal.Error{Status: http.StatusNotFound, Message: "User not found"} 95 errUnauthorized = &internal.Error{Status: http.StatusUnauthorized, Message: "Invalid username or password"} 96 ) 97 98 // Validate returns a user context object if the credentials are valid. 99 func (s *MemoryUserStore) Validate(_ context.Context, username, password string) (*UserContext, error) { 100 user, ok := s.users.Load(username) 101 if !ok { 102 return nil, errNotFound 103 } 104 if user.(*memoryUser).Password != password { 105 return nil, errUnauthorized 106 } 107 return &UserContext{ 108 Name: username, 109 Salt: user.(*memoryUser).Salt, 110 Roles: user.(*memoryUser).Roles, 111 }, nil 112 } 113 114 // UserCtx returns a user context object if the user exists. 115 func (s *MemoryUserStore) UserCtx(_ context.Context, username string) (*UserContext, error) { 116 user, ok := s.users.Load(username) 117 if !ok { 118 return nil, errNotFound 119 } 120 return &UserContext{ 121 Name: username, 122 Salt: user.(*memoryUser).Salt, 123 Roles: user.(*memoryUser).Roles, 124 }, nil 125 }