Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion local_dev.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export FLASK_APP=yt_pubsub_handler
export FLASK_ENV=development
flask run
flask run --cert=adhoc
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ attrs==20.3.0
boto3==1.17.84
botocore==1.20.84
certifi==2020.12.5
cffi==1.14.6
cfn-flip==1.2.3
chardet==4.0.0
click==7.1.2
cryptography==3.4.7
durationpy==0.5
Flask==1.1.2
Flask-Login==0.5.0
Flask-Migrate==3.0.1
flask-ngrok==0.0.25
Flask-SQLAlchemy==2.5.1
Expand All @@ -25,6 +28,7 @@ kappa==0.6.0
Mako==1.1.4
MarkupSafe==1.1.1
mysql-connector-python==8.0.25
oauthlib==3.1.1
packaging==20.9
pep517==0.10.0
pip-tools==6.1.0
Expand All @@ -36,6 +40,8 @@ protobuf==3.17.3
psycopg2-binary==2.8.6
py==1.10.0
pycodestyle==2.7.0
pycparser==2.20
pyOpenSSL==20.0.1
pyparsing==2.4.7
pytest==6.2.3
python-dateutil==2.8.1
Expand Down
32 changes: 31 additions & 1 deletion yt_pubsub_handler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import os

from flask import Flask, render_template
from flask_login import (
LoginManager,
current_user
)
from oauthlib.oauth2 import WebApplicationClient

from . import models

# Configuration
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", None)
GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", None)
GOOGLE_DISCOVERY_URL = (
"https://accounts.google.com/.well-known/openid-configuration"
)

db = models.db
login_manager = LoginManager()
client = WebApplicationClient(GOOGLE_CLIENT_ID)


# Flask-Login helper to retrieve a user from our db
@login_manager.user_loader
def load_user(user_id):
from .user import User
return User.get(user_id)


def create_app(test_config=None):
Expand All @@ -30,7 +51,7 @@ def create_app(test_config=None):

@app.route("/")
def index():
return render_template("index.html")
return render_template("index.html", current_user=current_user)


@app.route("/renew_leases")
Expand All @@ -43,13 +64,22 @@ def renew_lease():
db.app = app
db.init_app(app)
# sets up flask init-db cmd
# create a local sqlite db if in dev mode
from . import db_utils
if os.getenv("FLASK_ENV") == "development":
db_utils.init_db()
db_utils.init_app(app)

from . import pubsubhub
app.register_blueprint(pubsubhub.bp)
from . import subscriptions
app.register_blueprint(subscriptions.bp)
from . import auth
app.register_blueprint(auth.bp)
from flask_migrate import Migrate
migrate = Migrate(app, db)

# user session management setup
login_manager.init_app(app)

return app
90 changes: 90 additions & 0 deletions yt_pubsub_handler/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from enum import unique
import json
from flask_login.utils import login_required

import requests
from flask import Blueprint, request, current_app, render_template, url_for, redirect
from flask_login import login_user, login_required, logout_user

from yt_pubsub_handler import GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_DISCOVERY_URL, client
from . import models
from .user import User

bp = Blueprint("auth", __name__, url_prefix="/auth")


def get_google_provider_cfg():
# TODO: make this more resilient
return requests.get(GOOGLE_DISCOVERY_URL, timeout=60).json()


@bp.route("/login")
def login():
# find out which URL to hit for Google login
google_provider_cfg = get_google_provider_cfg()
auth_endpoint = google_provider_cfg["authorization_endpoint"]

# Use library to construc tht request for Google login
# and provide scopes that let you retrieve user's profile from Google
request_uri = client.prepare_request_uri(
auth_endpoint,
redirect_uri=request.url_root + "auth/login/callback",
scope=["openid", "email"]
)
return redirect(request_uri)


@bp.route("/login/callback")
def callback():
# Get authorization code Google sends back
code = request.args.get("code")

google_provider_cfg = get_google_provider_cfg()
token_endpoint = google_provider_cfg["token_endpoint"]

token_url, headers, body = client.prepare_token_request(
token_endpoint,
authorization_response=request.url,
redirect_url=request.base_url,
code=code
)

token_response = requests.post(
token_url,
headers=headers,
data=body,
auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
)

# parse the tokens
client.parse_request_body_response(json.dumps(token_response.json()))
# get user info
userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
uri, headers, body = client.add_token(userinfo_endpoint)
userinfo_response = requests.get(uri, headers=headers, data=body)

# ensure that email is verified
if userinfo_response.json().get("email_verified"):
unique_id = userinfo_response.json()["sub"]
users_email = userinfo_response.json()["email"]
else:
return "User email not available or not verified by Google.", 400

# create a user in the db
user = User(
id_=unique_id,
email=users_email
)

if not User.get(unique_id):
User.create(unique_id, users_email)

login_user(user)
return redirect(url_for("index"))


@bp.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("index"))
8 changes: 8 additions & 0 deletions yt_pubsub_handler/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ class Subscription(db.Model):

def __repr__(self):
return f"Subscription {self.id} {self.channel_id} {self.subreddit}>"


class User(db.Model):
id = db.Column(db.String(), primary_key=True)
email = db.Column(db.String(), unique=True, nullable=False)

def __repr__(self):
return f"User {self.id} {self.email}>"
29 changes: 29 additions & 0 deletions yt_pubsub_handler/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from flask_login import UserMixin
from yt_pubsub_handler import db
from . import models


class User(UserMixin):
def __init__(self, id_, email):
self.id = id_
self.email = email

@staticmethod
def get(user_id):
user = models.User.query.filter_by(id=user_id).first()
if not user:
return None
user = User(
id_=user.id,
email=user.email
)
return user

@staticmethod
def create(id_, email):
new_user = models.User(
id=str(id_),
email=email
)
db.session.add(new_user)
db.session.commit()