pallets-eco/flask-social

Unique Oauth connections for users

Closed this issue · 7 comments

Is there a way to ensure that users who register or connect with an Oauth account are not using this Oauth'ed account more than once across the system? In other words, can we prevent users from connecting or using their Twitter account on multiple accounts on our site? I tried googling and searching the docs, to no avail. I assume this would be a good feature for most sites.

If you are using a SQL datastore, you can set a unique constraint on provider_id and provider_user_id. These types of constraints are typically part of the datastore, not part of the application.

@mattupstate I am not sure if the code you linked to is related to what I was talking about, as that code seems to handle connections. Not registrations, which is what I was trying to prevent. Then again, I don't know the innards of the code so maybe I am wrong.

I think what @eriktaubeneck said was what I was thinking. I added:

__table_args__ = (db.UniqueConstraint('provider_id', 'provider_user_id', name='_providerid_userid_uc'), {})

but it didn't seem to do much. However, I was using sqlite and not MySQL (which I am now). I'll check this out again and see if it works. If so, I'll submit a PR for a documentation update.

Adding a unique constraint on those columns helps, as instead of letting the same OAuth account be connected to two different accound, I now get an error from Flask when doing so (with somenumbers replacing my ID):

IntegrityError: (IntegrityError) (1062, "Duplicate entry 'twitter-somenumbers' for key '_providerid_userid_uc'") 'INSERT INTO connections (user_id, provider_id, provider_user_id, access_token, secret, full_name, display_name, profile_url, image_url, rank) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)' (u'4', 'twitter', 'somenumbers', 'somenumbers-randomstring', 'another-random-string', 'twitter-handle', '@'twitter-handle', 'http://twitter.com/'twitter-handle', 'https://pbs.twimg.com/profile_images/etc/etc.jpeg', None)

The lines from the trace back that seem most useful are:

File "/home/james/Development/Credacious.com/credacious-flask/flask-env/lib/python2.7/site-packages/flask_social/views.py", line 38, in _commit

_datastore.commit()

File "/home/james/Development/Credacious.com/credacious-flask/flask-env/lib/python2.7/site-packages/flask_security/datastore.py", line 31, in commit

self.db.session.commit()

I am a bit of a newbie at Flask. Is there a way to catch this Exception at the app level, or would we need to add error handling in Flask-Security?

Not all users will want this to be unique (and the error will depend on which sort of Connection you are using) so it should be handled in your app. Just put the operation that raises the error in a try/except block, and then surface some sort of error message to the user that it's already connected to an account.

Sorry for not getting back to this. I understand what you are saying, but when you say "Just put the operation that raises the error in a try/except block," ... the code that does this is in Flask-Social itself. So I do not think we want to change the modue based on what you said. So my question is, can this be caught at the app level and if so, how?

If you application is set up similar to the Flask-Social-Example, then when in the view where the user is created, the commit will be the top of the stack trace that causes the error to occur. (In Flask-Social-Example, that happens here.) If you change that to something like:

try:
    ds.commit()
except IntegrityError:
    #flash a warning or something

that should take care of the issue.