singingwolfboy / flask-dance

Doing the OAuth dance with style using Flask, requests, and oauthlib.
https://pypi.python.org/pypi/Flask-Dance/
MIT License
1.01k stars 158 forks source link

Differentiate between sign up and sign in? #365

Open MRB60 opened 3 years ago

MRB60 commented 3 years ago

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.

janpeterka commented 1 year 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).