Open MRB60 opened 3 years ago
Hi, I was looking into this and didn't find a solution anywhere, so I came up with my own solution.
Main thing is creating three endpoints that only save value to session and redirect to blueprint.login
. Callback logic then loads that and does it's thing:
from flask import flash
from flask_security import current_user, login_user, url_for_security
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 flask import redirect, url_for, session
from app import db
from .models import OAuth
def create_module(application):
application.register_blueprint(blueprint, url_prefix="/oauth/")
application
blueprint = make_google_blueprint(
scope=["profile", "email"],
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user),
)
@blueprint.route("/google/login")
def auth_google_login():
session["auth_type"] = "login"
return redirect(url_for("google.login"))
@blueprint.route("/google/register")
def auth_google_register():
session["auth_type"] = "register"
return redirect(url_for("google.login"))
@blueprint.route("/google/add")
def auth_google_add_to_account():
session["auth_type"] = "add"
return redirect(url_for("google.login"))
# create/login local user on successful OAuth login
@oauth_authorized.connect_via(blueprint)
def google_logged_in(blueprint, token):
auth_type = session.pop("auth_type", "login")
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()
email = info["email"]
user_id = info["id"]
name = info["name"]
oauth = OAuth.query.filter_by(provider=blueprint.name, provider_user_id=user_id).first() # fmt: skip
if auth_type == "login":
if not oauth:
flash("not registered with Google, sorry")
return redirect(url_for_security("login"))
else:
login_user(oauth.user)
elif auth_type == "register":
if not oauth:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=user_id,
token=token,
username=email,
)
# Create a new local user account for this user
from app.services import UserManager
user = UserManager.create_or_load_user(email=email, password="x", name=name)
# 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)
# flash("Successfully signed in.")
elif auth_type == "add":
if oauth and oauth.user == current_user:
flash("You already connected google account.", "error")
return redirect(url_for("MemberSettingView:index"))
elif oauth:
flash("You cannot use this google account.", "error")
return redirect(url_for("MemberSettingView:index"))
else:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=user_id,
token=token,
username=email,
)
oauth.user = current_user
db.session.add(oauth)
db.session.commit()
flash("You can use Google to login to your account")
return redirect(url_for("MemberSettingView:index"))
# 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")
I can imagine this being implemented into blueprints directly - having three endpoints and three callbacks (or less based on config).
Probably an old issue but due to some legal aspects I might want to make a difference between sign up/register and sign in/log in. Is there a way to e,g. pass a client or consumer reference in the request(s) to provider which later can be accessed in @oauth_authorized.connect_via() ? This to know if request was issued from sign up or a sign in. I want to avoid automatic creation of a new user in case sign in was requested from the client.
At the moment I trigger the process for both cases with the same URL with no parameters.