Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced 'Remember me' functionality #470

Merged
merged 4 commits into from Feb 11, 2019
Merged

Conversation

vansika
Copy link
Member

@vansika vansika commented Dec 17, 2018

  • This is a…
    • Bug fix
    • Feature addition
    • Refactoring
    • Minor / simple change (like a typo)
    • Other

Problem

Some users note that they get logged out of ListenBrainz quite often.

Solution

Introduced a 'Remember me' checkbox and linked it with 'Sign in with Musicbrainz' button. Created a form to send checkbox data to server. If the checkbox is checked then the user will be logged in for specified duration even if the browser is accidentally closed.
Have cleared session before logging out to clear the 'Remember me' token that hinders signing out.

@alastair alastair self-assigned this Dec 17, 2018
Copy link
Collaborator

@alastair alastair left a comment

Choose a reason for hiding this comment

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

Hello!
Thank you for your contribution to ListenBrainz! When you create a pull request, please also add a link to a ticket if one exists (https://tickets.metabrainz.org/projects/LB/issues/LB-398?filter=allopenissues)

I'd like to make a few suggestions to this change. You'll note that in the ticket we added a number of items that should be checked before completing this task. It would be great if you could test the following items and add a comment to the ticket explaining what you learned:

  • How long is the default flask session time currently?
  • What is the behaviour of flask if we have a remember-me cookie set but we change the application secret key? Does this cause a login, or a logout?

As a next step, we can implement flask's remember_me option. There are a few items in the ticket that we didn't clarify, which we should have. Sorry about that. Keep in mind these things:

  • Use a 1 year timeout, like CatQuest suggested. It would be a good idea to make this explicit in case it changes in flask_login in the future
  • Yes, we need to use alternate tokens, as described in the documentation, this means we will need to make a database change and make some changes to the user model
  • We will need to disable session cookies on API requests
  • We don't need a checkbox letting the user select to use 'remember me', instead we should always use this setting. This will keep the login process simple.

As a side note, this is a good place to suggest that if you're unsure about what changes to make, you're absolutely welcome to add a comment to an open ticket asking questions, or telling us what you plan to do. This way we can give feedback before you start.

Please have a look at the initial questions that I mentioned above and see if you can answer them. Once this is done we can continue with the next steps to implement this functionality.

Thanks again for your contributions!

@vansika
Copy link
Member Author

vansika commented Dec 19, 2018

Hello @alastair
Thank you for the feedback. I will keep in mind to add appropriate links.

I have added comments to the ticket. Please have a look:)

Wouldn't it be good if we keep the remember me checkbox? Some users might wish to be remembered and some might not :)
The default remember me time is one year, though we can add it explicitly (through config.py) to be able to change it in future.
I will start working on the alternate tokens, and then other requirements after feedback from you.

Thank you.

@vansika
Copy link
Member Author

vansika commented Dec 23, 2018

Hello @alastair

I implemented alternate id, making changes in database and other files. Everything is working fine on localhost, when I change alternate id I get logged out otherwise the session is restored according to remember me cookie.
The logs seem fine yet there are errors while passing tests. The get_id method of user class is returning alt_id which is a string (have checked that) yet the tests say that it returns an integer.
Any help would be great.
Thankyou.

Copy link
Collaborator

@paramsingh paramsingh left a comment

Choose a reason for hiding this comment

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

I think we can have a better name for the alt_id column, maybe something that describes how we're using it in the login process.

@@ -12,6 +12,7 @@ CREATE TABLE "user" (
);
ALTER TABLE "user" ADD CONSTRAINT user_musicbrainz_id_key UNIQUE (musicbrainz_id);
ALTER TABLE "user" ADD CONSTRAINT user_musicbrainz_row_id_key UNIQUE (musicbrainz_row_id);
ALTER TABLE "user" ADD COLUMN alt_id VARCHAR;
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vansika, the bug seems to be here, you're declaring the alt_id column as varchar, but then comparing it with integers later.

Also, this column should be inside the CREATE TABLE statement instead of being a seperate ALTER statement.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please note that the automated tests on travis do a fresh build of the code you push, while your local environment may be different because of experimentation etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

@paramsingh, get_id( ) passes alt_id to load_user( ) which is a string, but the redirects in tests are passing user_id to load_user( ), which is the cause of error because the check function (filter_by_alt_id( )) inside load_user( ) can return user by checking against alt_id (string) and not user_id (integer).
I will try to debug. Thank you

@@ -117,3 +117,6 @@ SPOTIFY_CALLBACK_URL = 'http://localhost/profile/connect-spotify/callback'
# We do not use this feature at all, so it is safe to set to False.
# See https://stackoverflow.com/a/33790196 for more details.
SQLALCHEMY_TRACK_MODIFICATIONS = False

# expiration of 'Remember me' cookie
days = 365
Copy link
Collaborator

Choose a reason for hiding this comment

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

This name can be more descriptive. SESSION_REMEMBER_ME_DURATION maybe.

WHERE alt_id = :alt_id
""".format(columns=','.join(USER_GET_COLUMNS))), {"alt_id": alt_id})
row = result.fetchone()
print (row)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This print can probably be removed.

@@ -29,7 +30,7 @@ def musicbrainz_post():
if provider.validate_post_login():
user = provider.get_user()
db_user.update_last_login(user.musicbrainz_id)
login_user(user)
login_user(user, remember=True, duration=datetime.timedelta(days=config.days))
Copy link
Collaborator

Choose a reason for hiding this comment

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

please use current_app.config['days'] here instead of importing config explicitly.

@@ -98,7 +98,7 @@ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
UPLOAD_FOLDER = "/tmp/lastfm-backup-upload"


API_URL = 'https://api.listenbrainz.org'
API_URL = 'http://localhost'
Copy link
Collaborator

Choose a reason for hiding this comment

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

this change isn't needed, in my opinion

@paramsingh
Copy link
Collaborator

Also, we try to do descriptive commit messages in comparison to Update <file>. See this guide for examples: https://juffalow.com/other/write-good-git-commit-message

@vansika vansika force-pushed the master branch 3 times, most recently from 1c03d66 to c985f70 Compare January 5, 2019 04:49
@vansika
Copy link
Member Author

vansika commented Jan 6, 2019

@alastair
I have implemented alternate tokens and the other requirements of the PR except disabling session cookies on API requests. API auth doesn't seem to involve flask-login, how exactly do i disable it.

@@ -12,6 +12,7 @@ CREATE TABLE "user" (
);
ALTER TABLE "user" ADD CONSTRAINT user_musicbrainz_id_key UNIQUE (musicbrainz_id);
ALTER TABLE "user" ADD CONSTRAINT user_musicbrainz_row_id_key UNIQUE (musicbrainz_row_id);
ALTER TABLE "user" ADD COLUMN user_login_id VARCHAR;
Copy link
Collaborator

Choose a reason for hiding this comment

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

this column should be added inside the create table function call instead of creating the table and then altering it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, it should be NOT NULL, I guess.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, If the only thing we're filling this column with is UUIDs, we should use the postgres uuid data type instead. (https://www.postgresql.org/docs/9.6/datatype-uuid.html)

This would make it more efficient. And you could set a default value for all users as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

"uuid-ossp" extension is not being created due to some missing files. I will try to fix it and then would use UUID and postgres functions to generate user_login_id.

Copy link
Member Author

Choose a reason for hiding this comment

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

It seems that docker entry point need environment variables,defining them results in other authentication problems. Could you help find a way?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've fixed the problem in #479. Thanks for the bug report! As soon as it gets merged, you can rebase this PR and everything should work fine. In the meanwhile, you can just install the extension in your "listenbrainz" db and continue.

Copy link
Collaborator

Choose a reason for hiding this comment

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

merged now. =)

BEGIN;

-- Add column for alternative user id for login purpose to user table
ALTER TABLE "user" ADD COLUMN user_login_id VARCHAR;
Copy link
Collaborator

Choose a reason for hiding this comment

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

we will need this script to fill out the column for existing users also, because each user must have a login ID, no matter what.

"""Get user with a specified login ID.

Args:
id (int): login ID of a user.
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should be updated because login ID of the user isn't an integer.

@alastair
Copy link
Collaborator

I have implemented alternate tokens and the other requirements of the PR except disabling session cookies on API requests. API auth doesn't seem to involve flask-login, how exactly do i disable it.

From the documentation, https://flask-login.readthedocs.io/en/latest/#disabling-session-cookie-for-apis, this step only appears to be necessary if we use header_loader or request_loader for authentication in the API. We don't use these, instead we do authentication manually using _validate_auth_header, so it looks like this step is unnecessary.

@vansika
Copy link
Member Author

vansika commented Jan 22, 2019

@alastair
Okay, thank you. Is there any thing else I should implement?

Copy link
Collaborator

@paramsingh paramsingh left a comment

Choose a reason for hiding this comment

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

💯

@vansika
Copy link
Member Author

vansika commented Feb 3, 2019

😊

@login_forbidden
def musicbrainz():
if request.method == 'POST':
if 'check' in request.form:
Copy link
Member

Choose a reason for hiding this comment

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

You could re-write this to
session['check'] in request.form
That would be more pythonic. :)

session.clear()
logout_user()
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious, why this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Session variables need to be cleared before logging out so that remember me cookie is deleted. Otherwise, user won't be able to log out.

self.assertIn('Your Listens', data)
mock_user_get.assert_called_with(user['user_login_id'])

self.assertIn('My Listens', data)
Copy link
Member

Choose a reason for hiding this comment

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

We've changed "Your Listens" to "My listens", so the "Your Listens" are no longer needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay

BEGIN;

-- Add column for alternative user id for login purpose to user table
ALTER TABLE "user" ADD COLUMN user_login_id UUID NOT NULL DEFAULT uuid_generate_v4();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I just noticed that there's no index on this field, there should be if we use it for lookups

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants