Skip to content

Conversation

@itsmechlark
Copy link
Contributor

Allows having a history of login sessions with flexible active session limits.

Related #71

@itsmechlark itsmechlark force-pushed the feat-session-traceable branch 4 times, most recently from 4f0ecdf to 0c4f06f Compare February 11, 2024 16:02
@coveralls
Copy link
Collaborator

coveralls commented Feb 14, 2024

Coverage Status

coverage: 98.321% (+0.2%) from 98.169%
when pulling 882d2a5 on itsmechlark:feat-session-traceable
into 125b0d0 on devise-security:main.

Copy link
Contributor

@dillonwelch dillonwelch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like merge conflicts now since you so kindly fixed the test suite in a separate PR.

Can you look at some of the changes you made to the test config and see if they could be pulled out separately as well? Would make the review on this one easier, which I want to be thorough on since it's a new module and impacts an existing module. Also, I want to get other changes in so I can cut a new minor version and then have this in a new major version.

Note that I'm reviewing this while very sleepy and will review again in the near future with a more clear mind.

end
end

# Removes old/inactive session if allowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be deactivating / updating records in a boolean method like this. It should just be returning true / false without any side effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of allow_limitable_authentication? is to:

  1. If session_traceable module is not included then it will always be true.
  2. If didn't matched to 1 and number of sessions is still within the allowable number of session then it will allow authentication.
  3. If didn't matched to 2 and will only reject authentication on limit then it will try to remove any expired session to accomodate new authentication using deactivate_timeout_sessions!.
  4. If didn't matched to 3 (which reject authentication is disabled) then it will expire the oldest session regardless if its already expired.
  5. If didn't matched to 4 then disallow authentication.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Steps 3-5 don't feel like they belong in this method - boolean methods should only be returning true / false based on whatever logic you're adding. Steps 1-2 seem fine and what this method should be used for.

return true unless devise_modules.include?(:session_traceable)

opts = session_traceable_condition(active: true)
return true if max_active_sessions > session_traceable_adapter.find_all(opts).size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a little trouble following here - what is the return of session_traceable_adapter and what are you doing with find_all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

session_traceable_adapter will always be the session_history_class ORM compatible on session_traceable module. It will count the number of active sessions to check whether it is already on its maximum allowable number of sessions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear to me why we can't just use SessionHistory.


# Log user session.
# @return [true] if session is allowed to log.
# @return [false] if session isn't allowed to log when already exceeded to `:max_active_sessions`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should indicate the allow_limitable_authentication? part as well.

end

# Update the `:last_accessed_at` to current time.
def update_traceable_token(token)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on making these update / expire methods bang methods?


# Allow customization of the session history class
mattr_accessor :session_history_class
@@session_history_class = 'SessionHistory'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why you added this? It adds a lot of complexity and I'm not sure what benefit it gives.

# Update the `:last_accessed_at` to current time.
def update_traceable_token(token)
record = find_traceable_by_token(token)
record.update_attribute_without_validatons_or_callbacks(:last_accessed_at, Time.current.utc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you should have a presence check like you have for expire_session_token.

Copy link
Contributor

@dillonwelch dillonwelch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pressed the button before I was done :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading through this again, this feels separate from the SessionTraceable module you're adding.

If this is the case, it feels like it would be better as a separate PR.

If this is not the case, please let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes will allow to maintain the original logic of session_limitable which only allow 1 session at a time when session_traceable module but when session_traceable is added then it add flexibility on session limitable to customize maximum sessions per user and allowing session to expire. On scenarios that a user already hit the limit then its either reject authentication or expire the oldest session.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is supposed to be a history record, it seems like it would be more granular to create a new record each time the session was accessed instead of having one record for the session with last_accessed_at being updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we create a session history every time a user is fetch (on warden.authenticate or on devise current_user) then it will create a lot of session histories. Let's say in a single session of a user every time rails controller is hit, a history which likely in a single session it will be thousand of requests.

The main purpose of last_accessed_at is to track when the specific session (which track using token) is being used. The data relationship will always be a user has many sessions and last activity is tracked.

@itsmechlark itsmechlark force-pushed the feat-session-traceable branch 2 times, most recently from 20d9954 to 5c1dee4 Compare February 15, 2024 09:20
@dillonwelch
Copy link
Contributor

Thank you for the quick responses. I will get to them soon.

@itsmechlark itsmechlark force-pushed the feat-session-traceable branch 2 times, most recently from 724168b to 6ee1cee Compare February 16, 2024 16:34
Copy link
Contributor

@dillonwelch dillonwelch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can fix the merge conflicts now since I merged in the other PR.

# @return [1] by default. This can be overridden by application logic as necessary.
def max_active_sessions
# Always fallback to default logic when session_traceable is not supported.
return 1 unless devise_modules.include?(:session_traceable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to have session_traceable turned on before allowing more than 1 active session?

return true unless devise_modules.include?(:session_traceable)

opts = session_traceable_condition(active: true)
return true if max_active_sessions > session_traceable_adapter.find_all(opts).size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear to me why we can't just use SessionHistory.

end
end

# Removes old/inactive session if allowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Steps 3-5 don't feel like they belong in this method - boolean methods should only be returning true / false based on whatever logic you're adding. Steps 1-2 seem fine and what this method should be used for.

@itsmechlark itsmechlark force-pushed the feat-session-traceable branch from 6ee1cee to 882d2a5 Compare February 22, 2024 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants