github.com/go-kivik/kivik/v4@v4.3.2/x/kivikd/authdb/usersdb/usersdb.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 usersdb provides auth facilities from a CouchDB _users database. 14 package usersdb 15 16 import ( 17 "context" 18 "crypto/sha1" 19 "errors" 20 "fmt" 21 "net/http" 22 23 "golang.org/x/crypto/pbkdf2" 24 25 "github.com/go-kivik/kivik/v4" 26 internal "github.com/go-kivik/kivik/v4/int/errors" 27 "github.com/go-kivik/kivik/v4/x/kivikd/authdb" 28 ) 29 30 type db struct { 31 *kivik.DB 32 } 33 34 var _ authdb.UserStore = &db{} 35 36 // New returns a new authdb.UserStore backed by a the provided database. 37 func New(userDB *kivik.DB) authdb.UserStore { 38 return &db{userDB} 39 } 40 41 type user struct { 42 Name string `json:"name"` 43 Roles []string `json:"roles"` 44 PasswordScheme string `json:"password_scheme,omitempty"` 45 Salt string `json:"salt,omitempty"` 46 Iterations int `json:"iterations,omitempty"` 47 DerivedKey string `json:"derived_key,omitempty"` 48 } 49 50 func (db *db) getUser(ctx context.Context, username string) (*user, error) { 51 var u user 52 if err := db.Get(ctx, kivik.UserPrefix+username, nil).ScanDoc(&u); err != nil { 53 return nil, err 54 } 55 return &u, nil 56 } 57 58 func (db *db) Validate(ctx context.Context, username, password string) (*authdb.UserContext, error) { 59 u, err := db.getUser(ctx, username) 60 if err != nil { 61 if kivik.HTTPStatus(err) == http.StatusNotFound { 62 err = &internal.Error{Status: http.StatusUnauthorized, Message: "unauthorized"} 63 } 64 return nil, err 65 } 66 67 switch u.PasswordScheme { 68 case "": 69 return nil, errors.New("no password scheme set for user") 70 case authdb.SchemePBKDF2: 71 default: 72 return nil, fmt.Errorf("unsupported password scheme: %s", u.PasswordScheme) 73 } 74 key := fmt.Sprintf("%x", pbkdf2.Key([]byte(password), []byte(u.Salt), u.Iterations, authdb.PBKDF2KeyLength, sha1.New)) 75 if key != u.DerivedKey { 76 return nil, &internal.Error{Status: http.StatusUnauthorized, Message: "unauthorized"} 77 } 78 return &authdb.UserContext{ 79 Name: u.Name, 80 Roles: u.Roles, 81 Salt: u.Salt, 82 }, nil 83 } 84 85 func (db *db) UserCtx(ctx context.Context, username string) (*authdb.UserContext, error) { 86 u, err := db.getUser(ctx, username) 87 if err != nil { 88 return nil, err 89 } 90 return &authdb.UserContext{ 91 Name: u.Name, 92 Roles: u.Roles, 93 Salt: u.Salt, 94 }, nil 95 }