...

Source file src/github.com/go-kivik/kivik/v4/x/kivikd/authdb/authdb.go

Documentation: github.com/go-kivik/kivik/v4/x/kivikd/authdb

     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 authdb provides a standard interface to an authentication user store
    14  // to be used by AuthHandlers.
    15  package authdb
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/hmac"
    21  	"crypto/sha1"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"strconv"
    27  	"time"
    28  
    29  	"golang.org/x/crypto/pbkdf2"
    30  )
    31  
    32  // A UserStore provides an AuthHandler with access to a user store for.
    33  type UserStore interface {
    34  	// Validate returns a user context object if the credentials are valid. An
    35  	// error must be returned otherwise. A Not Found error must not be returned.
    36  	// Not Found should be treated identically to Unauthorized.
    37  	Validate(ctx context.Context, username, password string) (user *UserContext, err error)
    38  	// UserCtx returns a user context object if the user exists. It is used by
    39  	// AuthHandlers that don't validate the password (e.g. Cookie auth).
    40  	UserCtx(ctx context.Context, username string) (user *UserContext, err error)
    41  }
    42  
    43  // PBKDF2KeyLength is the key length, in bytes, of the PBKDF2 keys used by
    44  // CouchDB.
    45  const PBKDF2KeyLength = 20
    46  
    47  // SchemePBKDF2 is the default CouchDB password scheme.
    48  const SchemePBKDF2 = "pbkdf2"
    49  
    50  // UserContext represents a CouchDB UserContext object.
    51  // See http://docs.couchdb.org/en/2.0.0/json-structure.html#userctx-object.
    52  type UserContext struct {
    53  	Database string   `json:"db,omitempty"`
    54  	Name     string   `json:"name"`
    55  	Roles    []string `json:"roles"`
    56  	// Salt is needed to calculate cookie tokens.
    57  	Salt string `json:"-"`
    58  }
    59  
    60  // ValidatePBKDF2 returns true if the calculated hash matches the derivedKey.
    61  func ValidatePBKDF2(password, salt, derivedKey string, iterations int) bool {
    62  	hash := fmt.Sprintf("%x", pbkdf2.Key([]byte(password), []byte(salt), iterations, PBKDF2KeyLength, sha1.New))
    63  	return hash == derivedKey
    64  }
    65  
    66  // CreateAuthToken hashes a username, salt, timestamp, and the server secret
    67  // into an authentication token.
    68  func CreateAuthToken(name, salt, secret string, time int64) string {
    69  	if secret == "" {
    70  		panic("secret must be set")
    71  	}
    72  	if salt == "" {
    73  		panic("salt must be set")
    74  	}
    75  	sessionData := fmt.Sprintf("%s:%X", name, time)
    76  	h := hmac.New(sha1.New, []byte(secret+salt))
    77  	_, _ = h.Write([]byte(sessionData))
    78  	hashData := string(h.Sum(nil))
    79  	return base64.RawURLEncoding.EncodeToString([]byte(sessionData + ":" + hashData))
    80  }
    81  
    82  // MarshalJSON satisfies the json.Marshaler interface.
    83  func (c *UserContext) MarshalJSON() ([]byte, error) {
    84  	roles := c.Roles
    85  	if roles == nil {
    86  		roles = []string{}
    87  	}
    88  	output := map[string]interface{}{
    89  		"roles": roles,
    90  	}
    91  	if c.Database != "" {
    92  		output["db"] = c.Database
    93  	}
    94  	if c.Name != "" {
    95  		output["name"] = c.Name
    96  	} else {
    97  		output["name"] = nil
    98  	}
    99  	return json.Marshal(output)
   100  }
   101  
   102  // DecodeAuthToken decodes an auth token, extracting the username and token
   103  // token creation time. To validate the authenticity of the token, use
   104  // ValidatePBKDF2().
   105  func DecodeAuthToken(token string) (username string, created time.Time, err error) {
   106  	payload, err := base64.RawURLEncoding.DecodeString(token)
   107  	if err != nil {
   108  		return username, created, err
   109  	}
   110  	const partCount = 3
   111  	parts := bytes.SplitN(payload, []byte(":"), partCount)
   112  	if len(parts) < partCount {
   113  		return username, created, errors.New("invalid payload")
   114  	}
   115  	seconds, err := strconv.ParseInt(string(parts[1]), 16, 64)
   116  	if err != nil {
   117  		return username, created, fmt.Errorf("invalid timestamp '%s'", string(parts[1]))
   118  	}
   119  	return string(parts[0]), time.Unix(seconds, 0), nil
   120  }
   121  

View as plain text