Google Sign in

This commit is contained in:
Daniel Grams 2020-07-01 08:50:41 +02:00
parent c797b450df
commit 370a78913b
14 changed files with 184 additions and 28 deletions

View File

@ -4,7 +4,8 @@ python manage.py db init
python manage.py db migrate
python manage.py db upgrade
## Developemt !!!
## Development only
python manage.py db history
python manage.py db downgrade
// reset git: migrations/versions
@ -12,18 +13,22 @@ python manage.py db migrate
python manage.py db upgrade
## Kill local detached server
lsof -i :5000
kill -9 PIDNUMBER
# i18n
## i18n
https://pythonhosted.org/Flask-BabelEx/
<https://pythonhosted.org/Flask-BabelEx/>
## Init
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel init -i messages.pot -d translations -l de
## Neue msgid's scannen und in *.po mergen
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel update -i messages.pot -d translations
## Nach dem Übersetzen
pybabel compile -d translations

5
app.py
View File

@ -24,6 +24,8 @@ app.config['SECURITY_TRACKABLE'] = True
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_SEND_REGISTER_EMAIL'] = False
app.config['LANGUAGES'] = ['en', 'de']
app.config['GOOGLE_OAUTH_CLIENT_ID'] = os.environ['GOOGLE_OAUTH_CLIENT_ID']
app.config['GOOGLE_OAUTH_CLIENT_SECRET'] = os.environ['GOOGLE_OAUTH_CLIENT_SECRET']
# Generate a nice key using secrets.token_urlsafe()
app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw')
@ -48,6 +50,9 @@ db = SQLAlchemy(app)
from models import EventCategory, Image, EventSuggestion, EventSuggestionDate, OrgOrAdminUnit, Actor, Place, Location, User, Role, AdminUnit, AdminUnitMember, AdminUnitMemberRole, OrgMember, OrgMemberRole, Organization, AdminUnitOrg, AdminUnitOrgRole, Event, EventDate
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)
from oauth import blueprint
app.register_blueprint(blueprint, url_prefix="/login")
berlin_tz = pytz.timezone('Europe/Berlin')
now = datetime.now(tz=berlin_tz)

View File

@ -7,6 +7,7 @@ Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
${imports if imports else ""}
# revision identifiers, used by Alembic.

View File

@ -0,0 +1,39 @@
"""empty message
Revision ID: abf0f671ba27
Revises: bbad7e33a780
Create Date: 2020-06-30 21:09:35.692876
"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
# revision identifiers, used by Alembic.
revision = 'abf0f671ba27'
down_revision = 'bbad7e33a780'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('flask_dance_oauth',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('provider', sa.String(length=50), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('token', sqlalchemy_utils.types.json.JSONType(), nullable=False),
sa.Column('provider_user_id', sa.String(length=256), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('provider_user_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('flask_dance_oauth')
# ### end Alembic commands ###

View File

@ -4,6 +4,7 @@ from sqlalchemy.orm import relationship, backref
from sqlalchemy.schema import CheckConstraint
from sqlalchemy import UniqueConstraint, Boolean, DateTime, Column, Integer, String, ForeignKey, Unicode, UnicodeText, Numeric, LargeBinary
from flask_security import UserMixin, RoleMixin
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin
import datetime
### Base
@ -59,6 +60,11 @@ class User(db.Model, UserMixin):
roles = relationship('Role', secondary='roles_users',
backref=backref('users', lazy='dynamic'))
class OAuth(OAuthConsumerMixin, db.Model):
provider_user_id = Column(String(256), unique=True, nullable=False)
user_id = Column(Integer(), ForeignKey('user.id'), nullable=False)
user = db.relationship('User')
### Organization
class OrgMemberRolesMembers(db.Model):

66
oauth.py Normal file
View File

@ -0,0 +1,66 @@
from flask import flash
from flask_security import current_user, login_user
from flask_dance.contrib.google import make_google_blueprint
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
from sqlalchemy.orm.exc import NoResultFound
from models import User, OAuth
from app import db, user_datastore
from flask_babelex import gettext
blueprint = make_google_blueprint(
scope=["profile", "email"],
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user),
)
# create/login local user on successful OAuth login
@oauth_authorized.connect_via(blueprint)
def google_logged_in(blueprint, token):
if not token:
flash("Failed to log in.", category="error")
return False
resp = blueprint.session.get("/oauth2/v1/userinfo")
if not resp.ok:
msg = "Failed to fetch user info."
flash(msg, category="error")
return False
info = resp.json()
user_id = info["id"]
# Find this OAuth token in the database, or create it
oauth = OAuth.query.filter_by(provider=blueprint.name, provider_user_id=user_id).first()
if oauth is None:
oauth = OAuth(provider=blueprint.name, provider_user_id=user_id, token=token)
if oauth.user:
login_user(oauth.user, authn_via=["google"])
user_datastore.commit()
flash(gettext("Successfully signed in."), 'success')
else:
# Create a new local user account for this user
user = user_datastore.create_user(email=info["email"])
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user, authn_via=["google"])
user_datastore.commit()
flash(gettext("Successfully signed in."), 'success')
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
# notify on OAuth provider error
@oauth_error.connect_via(blueprint)
def google_error(blueprint, message, response):
msg = "OAuth error from {name}! message={message} response={response}".format(
name=blueprint.name, message=message, response=response
)
flash(msg, category="error")

View File

@ -2,7 +2,9 @@ alembic==1.4.2
Babel==2.8.0
bcrypt==3.1.7
blinker==1.4
certifi==2020.6.20
cffi==1.14.0
chardet==3.0.4
click==7.1.2
dnspython==1.16.0
dominate==2.5.1
@ -10,6 +12,7 @@ email-validator==1.1.1
Flask==1.1.2
Flask-BabelEx==0.9.4
Flask-Bootstrap==3.3.7.1
Flask-Dance==3.0.0
Flask-Login==0.5.0
Flask-Mail==0.9.1
Flask-Migrate==2.5.3
@ -24,6 +27,7 @@ itsdangerous==1.1.0
Jinja2==2.11.2
Mako==1.1.3
MarkupSafe==1.1.1
oauthlib==3.1.0
passlib==1.7.2
psycopg2-binary==2.8.5
pycparser==2.20
@ -31,9 +35,14 @@ python-dateutil==2.8.1
python-dotenv==0.13.0
python-editor==1.0.4
pytz==2020.1
requests==2.24.0
requests-oauthlib==1.3.0
six==1.15.0
speaklater==1.3
SQLAlchemy==1.3.17
SQLAlchemy-Utils==0.36.6
urllib3==1.25.9
URLObject==2.4.3
visitor==0.1.3
Werkzeug==1.0.1
WTForms==2.3.1

View File

@ -77,6 +77,11 @@ tr.table-line-through td {
z-index: 1025 !important;
}
.btn-google {
color: white;
background-color: #ea4335;
}
@media (max-width: 320px) {
td, th {
word-break: break-all;

View File

@ -193,3 +193,7 @@
</div>
</div>
{% endmacro %}
{% macro render_google_sign_in_button() %}
<a href="{{ url_for('google.login') }}" class="btn btn-google" role="button"><i class="fab fa-google mr-2"></i> {{ _('Sign in with Google') }}</a>
{% endmacro %}

View File

@ -13,7 +13,7 @@
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='site.css')}}" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/css/select2.min.css" rel="stylesheet" />
<!-- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='jquery.recurrenceinput.css')}}" /> -->

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_errors, render_field, render_field_errors %}
{% from "_macros.html" import render_google_sign_in_button, render_field_with_errors, render_field, render_field_errors %}
{% block content %}
@ -18,4 +18,8 @@
{{ render_field(login_user_form.submit) }}
</form>
<hr class="my-4">
{{ render_google_sign_in_button() }}
{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_errors, render_field %}
{% from "_macros.html" import render_google_sign_in_button, render_field_with_errors, render_field %}
{% block content %}
@ -14,4 +14,8 @@
{{ render_field(register_user_form.submit) }}
</form>
<hr class="my-4">
{{ render_google_sign_in_button() }}
{% endblock %}

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-06-26 12:23+0200\n"
"POT-Creation-Date: 2020-07-01 08:43+0200\n"
"PO-Revision-Date: 2020-06-07 18:51+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@ -18,90 +18,94 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: app.py:61
#: app.py:66
msgid "Event_"
msgstr ""
#: app.py:66
#: app.py:71
msgid "Event_Art"
msgstr "Kunst"
#: app.py:67
#: app.py:72
msgid "Event_Book"
msgstr "Literatur"
#: app.py:68
#: app.py:73
msgid "Event_Movie"
msgstr "Film"
#: app.py:69
#: app.py:74
msgid "Event_Family"
msgstr "Familie"
#: app.py:70
#: app.py:75
msgid "Event_Festival"
msgstr "Festival"
#: app.py:71
#: app.py:76
msgid "Event_Religious"
msgstr "Religion"
#: app.py:72
#: app.py:77
msgid "Event_Shopping"
msgstr "Shopping"
#: app.py:73
#: app.py:78
msgid "Event_Comedy"
msgstr "Comedy"
#: app.py:74
#: app.py:79
msgid "Event_Music"
msgstr "Musik"
#: app.py:75
#: app.py:80
msgid "Event_Dance"
msgstr "Tanz"
#: app.py:76
#: app.py:81
msgid "Event_Nightlife"
msgstr "Party"
#: app.py:77
#: app.py:82
msgid "Event_Theater"
msgstr "Theater"
#: app.py:78
#: app.py:83
msgid "Event_Dining"
msgstr "Essen"
#: app.py:79
#: app.py:84
msgid "Event_Conference"
msgstr "Konferenz"
#: app.py:80
#: app.py:85
msgid "Event_Meetup"
msgstr "Networking"
#: app.py:81
#: app.py:86
msgid "Event_Fitness"
msgstr "Fitness"
#: app.py:82
#: app.py:87
msgid "Event_Sports"
msgstr "Sport"
#: app.py:83
#: app.py:88
msgid "Event_Other"
msgstr "Sonstiges"
#: app.py:1062
#: app.py:1067
msgid "Event successfully created"
msgstr "Veranstaltung erfolgreich erstellt"
#: app.py:1094
#: app.py:1099
msgid "Event suggestion successfully created"
msgstr "Veranstaltungsvorschlag erfolgreich erstellt"
#: oauth.py:41 oauth.py:54
msgid "Successfully signed in."
msgstr "Erfolgreich eingeloggt."
#: templates/_macros.html:72 templates/event/create.html:6
msgid "Create event"
msgstr "Veranstaltung erstellen"
@ -164,6 +168,10 @@ msgstr "Link"
msgid "Category"
msgstr "Kategorie"
#: templates/_macros.html:198
msgid "Sign in with Google"
msgstr "Mit Google anmelden"
#: templates/admin_unit.html:14 templates/organization.html:17
msgid "Members"
msgstr "Mitglieder"