github.com/go-kivik/kivik/v4@v4.3.2/x/kivikd/authdb/confadmin/confadmin.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 //go:build !js 14 15 package confadmin 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/http" 22 "strconv" 23 "strings" 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 "github.com/go-kivik/kivik/v4/x/kivikd/conf" 29 ) 30 31 type confadmin struct { 32 *conf.Conf 33 } 34 35 var _ authdb.UserStore = &confadmin{} 36 37 // New returns a new confadmin authentication service provider. 38 func New(c *conf.Conf) authdb.UserStore { 39 return &confadmin{c} 40 } 41 42 func (c *confadmin) Validate(_ context.Context, username, password string) (*authdb.UserContext, error) { 43 derivedKey, salt, iterations, err := c.getKeySaltIter(username) 44 if err != nil { 45 if kivik.HTTPStatus(err) == http.StatusNotFound { 46 return nil, &internal.Error{Status: http.StatusUnauthorized, Message: "unauthorized"} 47 } 48 return nil, fmt.Errorf("unrecognized password hash: %w", err) 49 } 50 if !authdb.ValidatePBKDF2(password, salt, derivedKey, iterations) { 51 return nil, &internal.Error{Status: http.StatusUnauthorized, Message: "unauthorized"} 52 } 53 return &authdb.UserContext{ 54 Name: username, 55 Roles: []string{"_admin"}, 56 Salt: salt, 57 }, nil 58 } 59 60 const hashPrefix = "-" + authdb.SchemePBKDF2 + "-" 61 62 func (c *confadmin) getKeySaltIter(username string) (key, salt string, iterations int, err error) { 63 confName := "admins." + username 64 if !c.IsSet(confName) { 65 return "", "", 0, &internal.Error{Status: http.StatusNotFound, Message: "user not found"} 66 } 67 hash := c.GetString(confName) 68 if !strings.HasPrefix(hash, hashPrefix) { 69 return "", "", 0, errors.New("unrecognized password scheme") 70 } 71 const partsWanted = 3 72 parts := strings.Split(strings.TrimPrefix(hash, hashPrefix), ",") 73 if len(parts) != partsWanted { 74 return "", "", 0, errors.New("unrecognized hash format") 75 } 76 if iterations, err = strconv.Atoi(parts[2]); err != nil { 77 return "", "", 0, errors.New("unrecognized has format") 78 } 79 return parts[0], parts[1], iterations, nil 80 } 81 82 func (c *confadmin) UserCtx(_ context.Context, username string) (*authdb.UserContext, error) { 83 _, salt, _, err := c.getKeySaltIter(username) 84 if err != nil { 85 if kivik.HTTPStatus(err) == http.StatusNotFound { 86 return nil, &internal.Error{Status: http.StatusNotFound, Message: "user does not exist"} 87 } 88 return nil, fmt.Errorf("unrecognized password hash: %w", err) 89 } 90 return &authdb.UserContext{ 91 Name: username, 92 Roles: []string{"_admin"}, 93 Salt: salt, 94 }, nil 95 }