Merge pull request #94 from simple-login/social-login

Social login improvements
This commit is contained in:
Son Nguyen Kim 2020-02-27 23:02:02 +07:00 committed by GitHub
commit 4358b2791b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 11 deletions

View File

@ -45,6 +45,10 @@ def auth_login():
elif not user.activated:
return jsonify(error="Account not activated"), 400
return jsonify(**auth_payload(user, device)), 200
def auth_payload(user, device) -> dict:
ret = {
"name": user.name,
"mfa_enabled": user.enable_otp,
@ -64,4 +68,4 @@ def auth_login():
ret["mfa_key"] = None
ret["api_key"] = api_key.code
return jsonify(**ret), 200
return ret

View File

@ -14,7 +14,7 @@ from app.config import (
)
from app.extensions import db
from app.log import LOG
from app.models import User
from app.models import User, SocialAuth
from .login_utils import after_login
from ...email_utils import can_be_used_as_personal_email, email_already_used
@ -143,4 +143,8 @@ def facebook_callback():
# reset the next_url to avoid user getting redirected at each login :)
session.pop("facebook_next_url", None)
if not SocialAuth.get_by(user_id=user.id, social="facebook"):
SocialAuth.create(user_id=user.id, social="facebook")
db.session.commit()
return after_login(user, next_url)

View File

@ -9,7 +9,7 @@ from app.config import GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, URL, DISABLE_REGI
from app.email_utils import can_be_used_as_personal_email, email_already_used
from app.extensions import db
from app.log import LOG
from app.models import User
from app.models import User, SocialAuth
from app.utils import encode_url
_authorization_base_url = "https://github.com/login/oauth/authorize"
@ -105,6 +105,10 @@ def github_callback():
flash(f"Welcome to SimpleLogin {user.name}!", "success")
if not SocialAuth.get_by(user_id=user.id, social="github"):
SocialAuth.create(user_id=user.id, social="github")
db.session.commit()
# The activation link contains the original page, for ex authorize page
next_url = request.args.get("next") if request.args else None

View File

@ -7,7 +7,7 @@ from app.auth.base import auth_bp
from app.config import URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, DISABLE_REGISTRATION
from app.extensions import db
from app.log import LOG
from app.models import User, File
from app.models import User, File, SocialAuth
from app.utils import random_string
from .login_utils import after_login
from ...email_utils import can_be_used_as_personal_email, email_already_used
@ -128,6 +128,10 @@ def google_callback():
# reset the next_url to avoid user getting redirected at each login :)
session.pop("google_next_url", None)
if not SocialAuth.get_by(user_id=user.id, social="google"):
SocialAuth.create(user_id=user.id, social="google")
db.session.commit()
return after_login(user, next_url)

View File

@ -102,8 +102,10 @@ class AliasGeneratorEnum(enum.Enum):
class User(db.Model, ModelMixin, UserMixin):
__tablename__ = "users"
email = db.Column(db.String(256), unique=True, nullable=False)
salt = db.Column(db.String(128), nullable=False)
password = db.Column(db.String(128), nullable=False)
salt = db.Column(db.String(128), nullable=True)
password = db.Column(db.String(128), nullable=True)
name = db.Column(db.String(128), nullable=False)
is_admin = db.Column(db.Boolean, nullable=False, default=False)
alias_generator = db.Column(
@ -156,11 +158,9 @@ class User(db.Model, ModelMixin, UserMixin):
def create(cls, email, name, password=None, **kwargs):
user: User = super(User, cls).create(email=email, name=name, **kwargs)
if not password:
# set a random password
password = random_string(20)
if password:
user.set_password(password)
user.set_password(password)
db.session.flush()
# create a first alias mail to show user how to use when they login
@ -241,6 +241,8 @@ class User(db.Model, ModelMixin, UserMixin):
self.password = password_hash
def check_password(self, password) -> bool:
if not self.password:
return False
password_hash = bcrypt.hashpw(password.encode(), self.salt.encode())
return self.password.encode() == password_hash
@ -351,6 +353,17 @@ class ResetPasswordCode(db.Model, ModelMixin):
return self.expired < arrow.now()
class SocialAuth(db.Model, ModelMixin):
"""Store how user authenticates with social login"""
user_id = db.Column(db.ForeignKey(User.id, ondelete="cascade"), nullable=False)
# name of the social login used, could be facebook, google or github
social = db.Column(db.String(128), nullable=False)
__table_args__ = (db.UniqueConstraint("user_id", "social", name="uq_social_auth"),)
# <<< OAUTH models >>>

View File

@ -0,0 +1,50 @@
"""empty message
Revision ID: 75093e7ded27
Revises: e3cb44b953f2
Create Date: 2020-02-27 22:26:25.068117
"""
import sqlalchemy_utils
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '75093e7ded27'
down_revision = 'e3cb44b953f2'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('social_auth',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('social', sa.String(length=128), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='cascade'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id', 'social', name='uq_social_auth')
)
op.alter_column('users', 'password',
existing_type=sa.VARCHAR(length=128),
nullable=True)
op.alter_column('users', 'salt',
existing_type=sa.VARCHAR(length=128),
nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('users', 'salt',
existing_type=sa.VARCHAR(length=128),
nullable=False)
op.alter_column('users', 'password',
existing_type=sa.VARCHAR(length=128),
nullable=False)
op.drop_table('social_auth')
# ### end Alembic commands ###

View File

@ -39,8 +39,9 @@ def test_construct_url():
def test_authorize_page_non_login_user(flask_client):
"""make sure to display login page for non-authenticated user"""
user = User.create("test@test.com", "test user")
client = Client.create_new("test client", user.id)
db.session.commit()
client = Client.create_new("test client", user.id)
db.session.commit()
r = flask_client.get(