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.7'
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_hash
attribute), 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_hash
attribute containing the encrypted value of a random Clear-Text Password; anypassword
value withinattribs
is 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.now
to the:session_expiry
value 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 eithernil
or 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_exists
indicates that the method was called with a Registered User as thecurrent_user
parameter.:user_already_created
indicates that the specifiedname
attribute matches a record that already exists in the underlying Repository.:user_creation_failed
indicates 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 |