cl-cognito

2018-01-31

cl-cognito: A Common Lisp Interface to Amazon Cognito.

The primary purpose of this libary is to be able to obtain Amazon Cognito access, id, and refresh tokens based on Amazon Cognito user pool credentials. A secondary purpose is to provide other Cognito services over time.

These Amazon Cognito objects are used in this interface:

username : Cognito username. See Cognito -> User Pools -> Users and Groups. The username is not the user-email.
password : Cognito password.
pool-id : See Cognito -> User Pools -> General Settings.
client-id : See Cognito -> App clients -> App client id.
service : "cognito-idp". Other values may be possible, but this package doesn't know how to deal with them.
client-secret : Optional. See Cognito -> App clients -> App client id -> Show More Details -> App client secret.
user-email : Cognito e-mail. See Cognito -> User Pools -> Users and Groups.
access-key : Your user security credentials. secret-key :

The nickname cognito can be used for the cl-cognito package.

[Function]
authenticate-user (username password pool-id client-id &key (service "cognito-idp") (client-secret nil) (user-email nil) (new-password nil) (new-full-name nil) (new-phone nil) (new-email nil))

    => result, code, response
    
    If **client-secret** is not nil then **user-email** must be provided.  On a password change where new user e-mail is required,
    both user-email and new-email must be the same.
    
    If new-password is not nil and a new password is required then the existing password will be changed.
    new-full-name, new-phone, and new-email will be sent to Congito if required and not nil. 
    
    On success, returns **result** which is a list of the form:
    
    ((:*ACCESS-TOKEN . "eyJraW...")
     (:*EXPIRES-IN . 3600)
     (:*ID-TOKEN . "eyJraW...")
     (:*REFRESH-TOKEN . "eyJjdH...")
     (:*TOKEN-TYPE . "Bearer")
     (:*TIMESTAMP . 3712515272))
    
    :*TIMESTAMP is the Lisp universal time when the Cognito request was made, so :*TIMESTAMP + :*EXPIRES-IN
    is when the tokens expire.  **cl-cognito** does not automatically refresh tokens.
    
    On error, **result** is nil.
    
    **code** is the HTTP response code.
    
    On success, **response** is nil.  On error, **response** is the decoded JSON response provided by Amazon.
    
    Example:
    
    An incorrect password will result in:
    
    result => NIL
    code => 400
    response => ((:----TYPE . "NotAuthorizedException")
                 (:MESSAGE . "Incorrect username or password."))
                 
    A password that must be changed might result in:
    
    result => ((:*CHALLENGE-NAME . "NEW_PASSWORD_REQUIRED")
               (:*CHALLENGE-PARAMETERS (:REQUIRED-ATTRIBUTES . "[]")
               (:USER-ATTRIBUTES . "..."))
               (:*SESSION . "..."))
    code => 200
    response => nil
                 
    A password that must be reset will result in:
    
    result => NIL
    code => 400
    response => ((:----TYPE . "PasswordResetRequiredException")
                 (:MESSAGE . "Password reset required for the user"))
                 
    Use confirm-forgot-password to reset the password.

[Function]
new-password-required? (result)

    Returns t if the result from **authenticate-user** indicates a new password is required.  If so,
    repeat the call to *authenticate-user** and set :new-passowrd to the new desired password.
    :new-full-name, :new-phone, and :new-mail may also need to be provided.
    

[Function]
new-password-attributes (result)

    Returns a list of the attributes required for a password change if the result from **authenticate-user**
    indicates a new password is required.  Example:
    
      ("userAttributes.email" "userAttributes.phone_number" "userAttributes.name")

[Function]
reauthenticate-user (username refresh-token pool-id client-id &key (service "cognito-idp") (client-secret nil) (user-email nil))

    => result, code, response
    
    Identical to **authenticate-user** but a **refresh-token** is provided in place of a password.
    If **client-secret** is not nil then *user-email* must be provided.

    ((:*ACCESS-TOKEN . "eyJraW...")
     (:*EXPIRES-IN . 3600)
     (:*ID-TOKEN . "eyJraW...")
     (:*TOKEN-TYPE . "Bearer")
     (:*TIMESTAMP . 3712528761))

    If **client-secret** is nil then it appears that AWS doesn't care what *username* is.  YMMV.

[Function]
forgot-password (username pool-id client-id &key (service "cognito-idp") (client-secret nil))

    Causes a confirmation code which can be used by confirm-forgot-password to be sent to the user.

    => result, code, response

    If successful, result is t.
    
    Note that this doesn't expire the old password -- the old password can still be used until changed
    by confirm-forgot-password.

[Function]
confirm-forgot-password (username confirmation-code new-password pool-id client-id &key (service "cognito-idp") (client-secret nil))

    Change password to new-password.
    confirmation-code can be an integer or a string (e.g. 307293 or "307293")
    
    => result, code, response

    If successful, result is t.
    

[Function]
change-password (access-token pool-id old-password new-password &key (service "cognito-idp"))

    Change passowrd
    
    => result, code response
    
    If successful, result is t
    

[Function]
sign-out (access-token pool-id &key (service "cognito-idp"))

    => result, code, response
    
    Global sign out user.
    
    Returns t on success, nil on failure.  **code** and **response** can be used to
    determine failure cause.

[Function]
list-users (pool-id access-key secret-key &key (service "cognito-idp") (pagination-token nil))

    List users in pool.  Note:  A non-nil pagination-token has not been tested.

    => result, code, response

    On success, result is the list of users and their associated information.

[Function]
admin-create-user (username pool-id temporary-password access-key secret-key
                                  key (service "cognito-idp") (delivery 'email) (force-alias nil)
                                  (message-action 'suppress) (user-attributes nil) (validation-data nil))

    delivery is 'email or 'sms or '(email sms)
    message-action is 'resend or 'suppress
    user-attributes is ((attribute1 . value1) (attribute2 . value2) ...)
    validation-data is ((attribute1 . value1) (attribute2 . value2) ...)

    => result, code, response

    Create user.  On success, **result** is the response from AdminCreateUser
    
    Example:
    
    (defun create-aws-user (username real-name company e-mail phone-number temporary-password)
      (cognito:admin-create-user username *pool-id* temporary-password *access-key*  *secret-key*
                                 :user-attributes `(("email" . ,e-mail)
                                                    ("email_verified" . "true")
                                                    ("name" . ,real-name)
                                                    ("phone_number" . ,phone-number)
                                                    ("custom:company" . ,company)))))
                                                      
    (create-aws-user "test-user" "John Doe" "John Doe, Inc." "john@doe.com" "+15556678329" "ChangeMe.")

    =>
    ((:*USER
     (:*ATTRIBUTES
     ((:*NAME . "sub") (:*VALUE . "4836786e-f43c-47d9-b2e2-076aad5d9d8e"))
     ((:*NAME . "email_verified") (:*VALUE . "true"))
     ((:*NAME . "name") (:*VALUE . "John Doe"))
     ((:*NAME . "phone_number") (:*VALUE . "+15556678329"))
     ((:*NAME . "email") (:*VALUE . "john@doe.com"))
     ((:*NAME . "custom:company") (:*VALUE . "John Doe, Inc.")))
     (:*ENABLED . T) (:*USER-CREATE-DATE . 1.5076493e9)
     (:*USER-LAST-MODIFIED-DATE . 1.5076493e9)
     (:*USER-STATUS . "FORCE_CHANGE_PASSWORD") (:*USERNAME . "test-user")))
    200
    nil

[Function]
admin-update-user-attributes (username pool-id attributes access-key secret-key &key (service "cognito-idp"))

    user-attributes is ((attribute1 . value1) (attribute2 . value2) ...)
    
    => result, code, response

    Update user attributes
    
    Example:
    
    (cognito:admin-update-user-attributes "test-user" *pool-id* '(("custom:company" . "Doe John, Ltd.")) *access-key* *secret-key*)
    
    =>
    T
    200
    NIL

[Function]
admin-get-user (username pool-id access-key secret-key &key (service "cognito-idp"))

    => result, code, response
    
    Get user attributes
    
    Example:
    
    (cognito:admin-update-user-attributes "test-user" *pool-id*  *access-key* *secret-key*)
    
    =>
    ((:*ENABLED . T)
     (:*USER-ATTRIBUTES
      ((:*NAME . "sub") (:*VALUE . "4836786e-f43c-47d9-b2e2-076aad5d9d8e"))
      ((:*NAME . "email_verified") (:*VALUE . "true"))
      ((:*NAME . "name") (:*VALUE . "John Doe"))
      ((:*NAME . "phone_number") (:*VALUE . "+15556678329"))
      ((:*NAME . "email") (:*VALUE . "john@doe.com"))
      ((:*NAME . "custom:company") (:*VALUE . "Doe John, Ltd.")))
     (:*USER-CREATE-DATE . 1.5076493e9) (:*USER-LAST-MODIFIED-DATE . 1.50765e9)
     (:*USER-STATUS . "FORCE_CHANGE_PASSWORD") (:*USERNAME . "test-user"))
    200
    NIL

[Function]
admin-reset-user-password (username pool-id access-key secret-key &key (service "cognito-idp"))

    => result, code, response

    Returns t on success, nil on failure.  **code** and **response** can be used to
    determine failure cause.

[Function]
admin-delete-user (username pool-id access-key secret-key &key (service "cognito-idp"))

    => result, code, response
    
    Delete user from pool.  
    
    Example:
    
    (cognito:admin-delete-user "test-user" *pool-id*  *access-key* *secret-key*)
    
    => T
       200
       NIL

BUGS

The URL to use to interact with Cognito is constructed by the private function (make-aws-url/s service_s region_s).
No services other than "cognito-idp" and no regions other than those in the US have been tested.

It isn't clear that defining authenticate-user to take :new-full-name, :new-email, and :new-phone is the best design choice.

HTTP engine

Dexador is used to process HTTPS requests. The code encapsulates this in one function, so it would be easy to use Drakma, instead.

Repository

https://github.com/stablecross/cl-cognito

License

cl-cognito is available under a BSD-like license. See the file LICENSE for details.

Contact

For any questions or comments, please feel free to email me, Bob Felts

Author
Bob Felts <wrf3@stablecross.com>
License
BSD