Add login with Google
This commit is contained in:
parent
9da0104e73
commit
ac9b165ca0
@ -23,6 +23,12 @@
|
||||
<div id="hidden-answer-form" class="form-group"></div>
|
||||
</div>
|
||||
<button id="save" name="save" type="submit" class="btn btn-primary">Save</button>
|
||||
|
||||
{% if request.path != '/admin/questions/add' %}
|
||||
<div class="mt-3">
|
||||
<a class='btn btn-secondary' href='{{ url_for('admin.create_question') }}'>Add Another Question</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import os
|
||||
|
||||
import flask
|
||||
from flask import request, render_template, jsonify, Flask, url_for, redirect, flash, session
|
||||
from flask_security import Security, SQLAlchemySessionUserDatastore, login_user, logout_user, current_user
|
||||
from QuizTheWord import database
|
||||
from QuizTheWord.admin import admin
|
||||
# from QuizTheWord import config
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
|
||||
app = Flask(__name__)
|
||||
environment_configuration = os.environ.get('CONFIGURATION_SETUP', "QuizTheWord.config.Development")
|
||||
@ -11,6 +13,15 @@ with app.app_context():
|
||||
app.config.from_object(environment_configuration)
|
||||
user_datastore = SQLAlchemySessionUserDatastore(database.get_session(), database.User, database.Role)
|
||||
security = Security(app, user_datastore, False)
|
||||
CONF_URL = "https://accounts.google.com/.well-known/openid-configuration"
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
name="google",
|
||||
server_metadata_url=CONF_URL,
|
||||
client_kwargs={
|
||||
"scope": "openid email profile"
|
||||
}
|
||||
)
|
||||
app.register_blueprint(admin.Admin)
|
||||
|
||||
|
||||
@ -102,8 +113,22 @@ def next_multiple_choice():
|
||||
return jsonify(None), 204
|
||||
|
||||
|
||||
def get_auth_url(redirect_uri, **kwargs):
|
||||
rv = oauth.google.create_authorization_url(redirect_uri, **kwargs)
|
||||
|
||||
if oauth.google.request_token_url:
|
||||
request_token = rv.pop('request_token', None)
|
||||
oauth.google._save_request_token(request_token)
|
||||
|
||||
oauth.google.save_authorize_data(request, redirect_uri=redirect_uri, **rv)
|
||||
return rv["url"]
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
redirect_uri = url_for("auth_google", _external=True)
|
||||
google_url = get_auth_url(redirect_uri)
|
||||
|
||||
next_page = request.args.get("next", default=url_for("index"))
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
@ -115,7 +140,27 @@ def login():
|
||||
return redirect(url_for("login"))
|
||||
login_user(user, remember=remember)
|
||||
return redirect(next_page)
|
||||
return render_template("login.html", title="login")
|
||||
return render_template("login.html", title="login", google_url=google_url)
|
||||
|
||||
|
||||
@app.route("/login/auth/google")
|
||||
def auth_google():
|
||||
token = oauth.google.authorize_access_token()
|
||||
user = oauth.google.parse_id_token(token)
|
||||
unique_id = user["sub"]
|
||||
email = user["email"]
|
||||
username = user["given_name"]
|
||||
user = database.get_user(google_id=unique_id)
|
||||
if user is not None:
|
||||
login_user(user)
|
||||
else:
|
||||
user = database.add_user(email, None, username, google_id=unique_id)
|
||||
if user is not None:
|
||||
login_user(user)
|
||||
else:
|
||||
flash("That email is already in use. Login with that email or choose a different account.")
|
||||
return redirect(url_for("login"))
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/register", methods=["GET", "POST"])
|
||||
|
@ -14,6 +14,8 @@ class Config:
|
||||
DB_PORT = environ.get("DB_PORT")
|
||||
DB_NAME = environ.get("DB_NAME")
|
||||
SECURITY_REGISTERABLE = True
|
||||
GOOGLE_CLIENT_ID = environ.get("GOOGLE_CLIENT_ID")
|
||||
GOOGLE_CLIENT_SECRET = environ.get("GOOGLE_CLIENT_SECRET")
|
||||
|
||||
|
||||
class Production(Config):
|
||||
|
@ -4,7 +4,7 @@ from flask_security import UserMixin, RoleMixin, SQLAlchemySessionUserDatastore,
|
||||
import sqlalchemy
|
||||
from typing import Union, Optional, Literal, Type, List, Tuple
|
||||
import random
|
||||
from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, Boolean, UnicodeText, DateTime
|
||||
from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, Boolean, UnicodeText, DateTime, Numeric, CheckConstraint
|
||||
from sqlalchemy.sql.expression import and_
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
@ -51,7 +51,7 @@ class User(Base, UserMixin):
|
||||
user_id = Column(Integer, primary_key=True)
|
||||
email = Column(String, unique=True, nullable=False)
|
||||
username = Column(String, unique=True)
|
||||
password = Column(String, nullable=False)
|
||||
password = Column(String, nullable=True)
|
||||
active = Column(Boolean, nullable=False)
|
||||
last_login_at = Column(DateTime())
|
||||
current_login_at = Column(DateTime())
|
||||
@ -59,6 +59,12 @@ class User(Base, UserMixin):
|
||||
current_login_ip = Column(String(100))
|
||||
login_count = Column(Integer)
|
||||
fs_uniquifier = Column(String, unique=True, nullable=False)
|
||||
google_id = Column(Numeric, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("(password IS NOT NULL) OR (google_id IS NOT NULL)", "password_google_id_null_check"),
|
||||
)
|
||||
|
||||
roles = relationship("Role", secondary="users_roles")
|
||||
|
||||
def check_password(self, password):
|
||||
@ -356,11 +362,14 @@ def update_question(question_id, data):
|
||||
session.commit()
|
||||
|
||||
|
||||
def add_user(email, password):
|
||||
def add_user(email, password, username=None, **kwargs):
|
||||
session = get_session()
|
||||
user_datastore = get_user_datastore()
|
||||
if user_datastore.find_user(email=email) is None:
|
||||
user = user_datastore.create_user(email=email, password=hash_password(password), username=email)
|
||||
password_hash = hash_password(password) if password is not None else None
|
||||
if username is None:
|
||||
username = email
|
||||
user = user_datastore.create_user(email=email, password=password_hash, username=username, **kwargs)
|
||||
session.add(user)
|
||||
role = session.query(Role).filter(Role.name == "basic").one_or_none()
|
||||
if role is not None:
|
||||
@ -375,9 +384,13 @@ def add_user(email, password):
|
||||
return None
|
||||
|
||||
|
||||
def get_user(user_id) -> User:
|
||||
def get_user(user_id=None, google_id=None) -> User:
|
||||
session = get_session()
|
||||
return session.query(User).filter(User.user_id == user_id).one_or_none()
|
||||
if user_id is not None:
|
||||
user = session.query(User).filter(User.user_id == user_id).one_or_none()
|
||||
else:
|
||||
user = session.query(User).filter(User.google_id == google_id).one_or_none()
|
||||
return user
|
||||
|
||||
|
||||
def update_user(user_id, data):
|
||||
|
BIN
QuizTheWord/static/images/sign_in_with_google.png
Normal file
BIN
QuizTheWord/static/images/sign_in_with_google.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
@ -29,6 +29,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{% if has_admin_access %}
|
||||
<li class="nav-item me-1">
|
||||
<a class="nav-link" href="{{ url_for('admin.index') }}">Admin</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if not user_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-primary" href="{{ url_for('login') }}">Login</a>
|
||||
|
@ -19,6 +19,11 @@
|
||||
</div>
|
||||
<button id="submit" name="submit" type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<a href="{{ google_url }}">
|
||||
<img src="/static/images/sign_in_with_google.png" alt="Sign in with Google">
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
Don't have an account? <a href="{{ url_for('register') }}">Register</a>
|
||||
</div>
|
||||
|
@ -13,4 +13,9 @@ Flask-Security-Too~=4.0.0
|
||||
pytest~=6.2.2
|
||||
gunicorn~=20.1.0
|
||||
html5validate~=0.0.2
|
||||
bcrypt
|
||||
Authlib~=0.15.4
|
||||
Flask-WTF~=0.14.3
|
||||
bcrypt~=3.2.0
|
||||
WTForms~=2.3.3
|
||||
html5validate~=0.0.2
|
||||
requests~=2.26.0
|
Loading…
Reference in New Issue
Block a user