anchor.diff:  ;;;
   ;;; Utils
   ;;;
  -(defun hash-password (password)
  +
  +;;; The password confirmation scheme uses PDKDF2. CouchDB's randomly-generated document id for the
  +;;; particular account is used as a salt for the password. The two parameters should be adjusted
  +;;; according to your server's capabilities. Do some profiling and make these numbers as high as you
  +;;; can without lagging under heavy load.
  +
  +(defparameter *key-derivation-iterations* 1056)
  +(defparameter *key-length* 32)
  +(defun hash-password (password salt)
     "Password hashing function."
  -  ;; TODO - maybe a salt?
  +  (ironclad:byte-array-to-hex-string
  +   (ironclad:derive-key
  +    (make-instance 'ironclad:pbkdf2 :digest :sha256)
  +    (ironclad:ascii-string-to-byte-array password)
  +    (ironclad:ascii-string-to-byte-array salt)
  +    *key-derivation-iterations*
  +    *key-length*))
  +  #+nil
     (ironclad:byte-array-to-hex-string
      (ironclad:digest-sequence
       :sha256
  
positive.code:     (assert-required "Confirmation" confirmation)
      (assert-validation (valid-email-p account-name) "Invalid email.")
      (assert-validation (not (find-account account-name)) "Account already exists.")
      (assert-validation (valid-password-p password) "Invalid password.")
      (assert-validation (valid-display-name-p display-name) "Invalid display name. Display name must be between 4 and 32 alphanumeric characters.")
      (assert-validation (not (find-account-by-display-name display-name)) "Display name already in use.")
      (assert-validation (string= password confirmation) "Password confirmation does not match.")))
  
  (defun create-account (account-name display-name password confirmation
                         &aux (hashed-pass (hash-password password)))
    (multiple-value-bind (validp errors)
        (validate-new-account account-name display-name password confirmation)
      (if validp
          (ensure-doc (get-uuid)
                      (mkdoc "type" "account"
                             "account_name" account-name
                             "display_name" display-name
                             "password" hashed-pass
                             "created_at" (get-universal-time)))
          (values nil errors))))
  
negative.code: (defun find-account (account-name)
    (account-view-value "by_account_name" (string-downcase account-name)))
  
  (defun validate-credentials (account-name password)
    (let* ((account (account-view-value "by_account_name" (string-downcase account-name)))
           (hashed-pass (hash-password password (doc-val account "_id"))))
      (when (string= hashed-pass (doc-val account "password"))
        account)))
  
  (defun account-name (account)
    (doc-val account "account_name"))
  