Module: CryptIdent
- Extended by:
- Dry::Configurable
- Defined in:
- lib/crypt_ident/config.rb,
lib/crypt_ident/sign_in.rb,
lib/crypt_ident/sign_up.rb,
lib/crypt_ident/version.rb,
lib/crypt_ident/sign_out.rb,
lib/crypt_ident/reset_password.rb,
lib/crypt_ident/change_password.rb,
lib/crypt_ident/session_expired.rb,
lib/crypt_ident/generate_reset_token.rb,
lib/crypt_ident/update_session_expiry.rb
Overview
Include and interact with CryptIdent to add authentication to a
Hanami controller action.
Note the emphasis on controller action; this module interacts with session data, which is quite theoretically possible in an Interactor but practically quite the PITA. YHBW.
Constant Summary collapse
- VERSION =
Version number for Gem. Uses Semantic Versioning.
'0.2.5'
Instance Method Summary collapse
-
#change_password(user_in, current_password, new_password) {|result| ... }
Change an Authenticated User's password.
-
#generate_reset_token(user_name, current_user: nil) {|result| ... }
Generate a Password Reset Token.
-
#reset_password(token, new_password, current_user: nil) {|result| ... }
Reset the password for the User associated with a Password Reset Token.
-
#session_expired?(session_data = {}) ⇒ Boolean
Determine whether the Session has Expired due to User inactivity.
-
#sign_in(user_in, password, current_user: nil) {|result| ... }
Attempt to Authenticate a User, passing in an Entity for that User (which must contain a
password_hashattribute), and a Clear-Text Password. -
#sign_out(current_user:) {|result| ... }
Sign out a previously Authenticated User.
-
#sign_up(attribs, current_user:) {|result| ... }
Persist a new User to a Repository based on passed-in attributes, where the resulting Entity (on success) contains a
:password_hashattribute containing the encrypted value of a random Clear-Text Password; anypasswordvalue withinattribsis ignored. -
#update_session_expiry(session_data = {}) ⇒ Hash
Generate a Hash containing an updated Session Expiration timestamp, which can then be used for session management.
Instance Method Details
#change_password(user_in, current_password, new_password) {|result| ... }
This method returns an undefined value.
Change an Authenticated User's password.
To change an Authenticated User's password, an Entity for that User, the
current Clear-Text Password, and the new Clear-Text Password are required.
The method accepts an optional repo parameter to specify a Repository
instance to which the updated User Entity should be persisted; if none is
specified (i.e., if the parameter has its default value of nil), then the
UserRepository specified in the Configuration is used.
The method requires a block, to which a result indicating success or
failure is yielded. That block must in turn call both
result.success and result.failure to handle success and failure results,
respectively. On success, the block yielded to by result.success is called
and passed a user: parameter, which is identical to the user parameter
passed in to #change_password except that the :password_hash attribute
has been updated to reflect the changed password. The updated value for the
encrypted password will also have been saved to the Repository.
On failure, the result.failure call will yield a code: parameter to its
block, which indicates the cause of failure as follows:
If the specified password did not match the passed-in user Entity, then
the code: for failure will be :bad_password.
If the specified user was other than a User Entity representing a
Registered User, then the code: for failure will be :invalid_user.
Note that no check for the Current User is done here; this method trusts the Controller Action Class that (possibly indirectly) invokes it to guard that contingency properly.
103 104 105 106 107 108 |
# File 'lib/crypt_ident/change_password.rb', line 103 def change_password(user_in, current_password, new_password) call_params = [current_password, new_password] ChangePassword.new(user: user_in).call(*call_params) do |result| yield result end end |
#generate_reset_token(user_name, current_user: nil) {|result| ... }
This method returns an undefined value.
Generate a Password Reset Token
Password Reset Tokens are useful for verifying that the person requesting a Password Reset for an existing User is sufficiently likely to be the person who Registered that User or, if not, that no compromise or other harm is done.
Typically, this is done by sending a link through email or other such medium
to the address previously associated with the User purportedly requesting
the Password Reset. CryptIdent does not automate generation or sending
of the email message. What it does provide is a method to generate a new
Password Reset Token to be embedded into an HTML anchor link within an email
that you construct, and then another method (#reset_password) to actually
change the password given a valid, correct token.
It also implements an expiry system, such that if the confirmation of the Password Reset request is not completed within a configurable time, that the token is no longer valid (and so cannot be later reused by unauthorised persons).
This method requires a block, to which a result indicating success or
failure is yielded. That block must in turn call both
result.success and result.failure to handle success and failure results,
respectively. On success, the block yielded to by result.success is called
and passed a user: parameter, which is identical to the user parameter
passed in to #generate_reset_token except that the :token and
:password_reset_expires_at attributes have been updated to reflect the
token request. An updated record matching that :user Entity will also have
been saved to the Repository.
On failure, the result.failure call will yield three parameters: :code,
:current_user, and :name, and will be set as follows:
If the :code value is :user_logged_in, that indicates that the
current_user parameter to this method represented a Registered User. In
this event, the :current_user value passed in to the result.failure call
will be the same User Entity passed into the method, and the :name value
will be :unassigned.
If the :code value is :user_not_found, the named User was not found in
the Repository. The :current_user parameter will be the Guest User Entity,
and the :name parameter to the result.failure block will be the
user_name value passed into the method.
113 114 115 116 117 118 |
# File 'lib/crypt_ident/generate_reset_token.rb', line 113 def generate_reset_token(user_name, current_user: nil) other_params = { current_user: current_user } GenerateResetToken.new.call(user_name, other_params) do |result| yield result end end |
#reset_password(token, new_password, current_user: nil) {|result| ... }
This method returns an undefined value.
Reset the password for the User associated with a Password Reset Token.
After a Password Reset Token has been generated and sent to a User, that User would then exercise the Client system and perform a Password Reset.
Calling #reset_password is different than calling #change_password in
one vital respect: with #change_password, the User involved must be
the Current User (as presumed by passing the appropriate User Entity in as
the current_user: parameter), whereas #reset_password must not be
called with any User other than the Guest User as the current_user:
parameter (and, again presumably, the Current User for the session). How can
we assure ourselves that the request is legitimate for a specific User? By
use of the Token generated by a previous call to #generate_reset_token,
which is used in place of a User Name for this request.
Given a valid set of parameters, and given that the updated User is
successfully persisted, the method calls the required block with a
result whose result.success matcher is yielded a user: parameter with
the updated User as its value.
NOTE: Each of the error returns documented below calls the required
block with a result whose result.failure matcher is yielded a code:
parameter as described, and a token: parameter that has the same value
as the passed-in token parameter.
If the passed-in token parameter matches the token field of a record in
the Repository and that Token is determined to have Expired, then the
code: parameter mentioned earlier will have the value :expired_token.
If the passed-in token parameter does not match the token field of any
record in the Repository, then the code: parameter will have the value
:token_not_found.
If the passed-in current_user: parameter is a Registered User, then the
code: parameter will have the value :invalid_current_user.
In no event are session values, including the Current User, changed. After a successful Password Reset, the User must Authenticate as usual.
111 112 113 114 115 116 |
# File 'lib/crypt_ident/reset_password.rb', line 111 def reset_password(token, new_password, current_user: nil) other_params = { current_user: current_user } ResetPassword.new.call(token, new_password, other_params) do |result| yield result end end |
#session_expired?(session_data = {}) ⇒ Boolean
Determine whether the Session has Expired due to User inactivity.
This is one of two methods in CryptIdent (the other being
#update_session_expiry?) which does not follow
the result/success/failure monad workflow. This is because
there is no success/failure division in the workflow. Calling the method
determines if the Current User session has Expired. If the passed-in
:current_user is a Registered User, then this will return true if the
current time is later than the passed-in :expires_at value; for the
Guest User, it should always return false. (Guest User sessions never
expire; after all, what would you change the session state to?).
The client code is responsible for applying these values to its own actual session data, as described by the sample session-management code shown in the README.
58 59 60 |
# File 'lib/crypt_ident/session_expired.rb', line 58 def session_expired?(session_data = {}) SessionExpired.new.call(session_data) end |
#sign_in(user_in, password, current_user: nil) {|result| ... }
This method returns an undefined value.
Attempt to Authenticate a User, passing in an Entity for that User (which
must contain a password_hash attribute), and a Clear-Text Password.
It also passes in the Current User.
If the Current User is not a Registered User, then Authentication of the
specified User Entity against the specified Password is accomplished by
comparing the User Entity's password_hash attribute to the passed-in
Clear-Text Password.
The method requires a block, to which a result indicating success or
failure is yielded. That block must in turn call both
result.success and result.failure to handle success and failure results,
respectively. On success, the block yielded to by result.success is called
and passed a user: parameter, which is the Authenticated User (and is the
same Entity as the user parameter passed in to #sign_in).
On failure, the result.failure call will yield a code: parameter to its
block, which indicates the cause of failure as follows:
If the specified password did not match the passed-in user Entity, then
the code: for failure will be :invalid_password.
If the specified user was not a Registered User, then the code: for
failure will be :user_is_guest.
If the specified current_user is neither the Guest User nor the user
passed in as a parameter to #sign_in, then the code: for failure will be
:illegal_current_user.
On success, the Controller-level client code must set:
session[:expires_at]to the expiration time for the session. This is ordinarily computed by adding the current time as returned byTime.nowto the:session_expiryvalue in the current configuration.session[:current_user]to tne returned Entity for the successfully Authenticated User. This is to eliminate possible repeated reads of the Repository.
On failure, the Controller-level client code should set:
session[:expires_at]to some sufficiently-past time to always trigger#session_expired?;Hanami::Utils::Kernel.Time(0)does this quite well (returning midnight GMT on 1 January 1970, converted to local time).session[:current_user]to eithernilor the Guest User.
123 124 125 126 |
# File 'lib/crypt_ident/sign_in.rb', line 123 def sign_in(user_in, password, current_user: nil) params = { user: user_in, password: password, current_user: current_user } SignIn.new.call(params) { |result| yield result } end |
#sign_out(current_user:) {|result| ... }
This method returns an undefined value.
Sign out a previously Authenticated User.
The method requires a block, to which a result indicating success or
failure is yielded. (Presently, any call to #sign_out results in success.)
That block must in turn call both result.success and
result.failure (even though no failure is implemented) to handle success
and failure results, respectively. On success, the block yielded to by
result.success is called without parameters.
74 75 76 |
# File 'lib/crypt_ident/sign_out.rb', line 74 def sign_out(current_user:) SignOut.new.call(current_user: current_user) { |result| yield result } end |
#sign_up(attribs, current_user:) {|result| ... }
This method returns an undefined value.
Persist a new User to a Repository based on passed-in attributes, where the
resulting Entity (on success) contains a :password_hash attribute
containing the encrypted value of a random Clear-Text Password; any
password value within attribs is ignored.
The method requires a block, to which a result indicating success or
failure is yielded. That block must in turn call both
result.success and result.failure to handle success and failure results,
respectively. On success, the block yielded to by result.success is called
and passed a user: parameter, which is the newly-created User Entity.
If the call fails, the result.success block is yielded to, and passed a
code: parameter, which will contain one of the following symbols:
:current_user_existsindicates that the method was called with a Registered User as thecurrent_userparameter.:user_already_createdindicates that the specifiednameattribute matches a record that already exists in the underlying Repository.:user_creation_failedindicates that the Repository was unable to create the new User for some other reason, such as an internal error.
NOTE that the incoming params are expected to have been whitelisted at
the Controller Action Class level.
87 88 89 90 91 |
# File 'lib/crypt_ident/sign_up.rb', line 87 def sign_up(attribs, current_user:) SignUp.new.call(attribs, current_user: current_user) do |result| yield result end end |
#update_session_expiry(session_data = {}) ⇒ Hash
Generate a Hash containing an updated Session Expiration timestamp, which can then be used for session management.
This is one of two methods in CryptIdent (the other being
#session_expired?) which does not follow the
result/success/failure monad workflow. This is because
there is no success/failure division in the workflow. Calling the method
only makes sense if there is a Registered User as the Current User, but all
this method does is build a Hash with :current_user and :expires_at
entries. The returned :current_user is the passed-in :current_user if a
Registered User, or the Guest User if not. The returned :updated_at value,
for a Registered User, is the configured Session Expiry added to the current
time, and for the Guest User, a time far enough in the future that any call
to #session_expired? will be highly unlikely to ever return true.
The client code is responsible for applying these values to its own actual session data, as described by the sample session-management code shown in the README.
74 75 76 |
# File 'lib/crypt_ident/update_session_expiry.rb', line 74 def update_session_expiry(session_data = {}) UpdateSessionExpiry.new.call(session_data) end |