Compare commits

..

2 commits

Author SHA1 Message Date
deflax 02bf209215 implement oauth2 2024-04-06 17:04:05 +03:00
deflax 04833ff7e0 provide oauth variables 2024-04-06 15:18:09 +03:00
5 changed files with 125 additions and 21 deletions

View file

@ -1,6 +1,7 @@
from flask import render_template, redirect, request, url_for, flash, session, abort, current_app from flask import render_template, redirect, request, url_for, flash, session, abort, current_app
from flask_login import login_required, login_user, logout_user, current_user from flask_login import login_required, login_user, logout_user, current_user
from markupsafe import Markup, escape from markupsafe import Markup, escape
from urllib.parse import urlencode
from . import auth from . import auth
from .forms import LoginForm, TwoFAForm, RegistrationForm, ChangePasswordForm, PasswordResetRequestForm, PasswordResetForm from .forms import LoginForm, TwoFAForm, RegistrationForm, ChangePasswordForm, PasswordResetRequestForm, PasswordResetForm
@ -12,19 +13,91 @@ from models import db, User
from io import BytesIO from io import BytesIO
import pyqrcode import pyqrcode
def get_google_auth(state=None, token=None): @app.route('/authorize/<provider>')
if token: def oauth2_authorize(provider):
return OAuth2Session(current_app.config['CLIENT_ID'], token=token) if not current_user.is_anonymous:
if state: return redirect(url_for('index'))
return OAuth2Session(
current_app.config['CLIENT_ID'], provider_data = current_app.config['OAUTH2_PROVIDERS'].get(provider)
state=state, if provider_data is None:
redirect_uri=current_app.config['REDIRECT_URI']) abort(404)
oauth = OAuth2Session(
current_app.config['CLIENT_ID'], # generate a random string for the state parameter
redirect_uri=current_app.config['REDIRECT_URI'], session['oauth2_state'] = secrets.token_urlsafe(16)
scope=current_app.config['SCOPE'])
return oauth # create a query string with all the OAuth2 parameters
qs = urlencode({
'client_id': provider_data['client_id'],
'redirect_uri': url_for('oauth2_callback', provider=provider,
_external=True),
'response_type': 'code',
'scope': ' '.join(provider_data['scopes']),
'state': session['oauth2_state'],
})
# redirect the user to the OAuth2 provider authorization URL
return redirect(provider_data['authorize_url'] + '?' + qs)
@app.route('/callback/<provider>')
def oauth2_callback(provider):
if not current_user.is_anonymous:
return redirect(url_for('index'))
provider_data = current_app.config['OAUTH2_PROVIDERS'].get(provider)
if provider_data is None:
abort(404)
# if there was an authentication error, flash the error messages and exit
if 'error' in request.args:
for k, v in request.args.items():
if k.startswith('error'):
flash(f'{k}: {v}')
return redirect(url_for('index'))
# make sure that the state parameter matches the one we created in the
# authorization request
if request.args['state'] != session.get('oauth2_state'):
abort(401)
# make sure that the authorization code is present
if 'code' not in request.args:
abort(401)
# exchange the authorization code for an access token
response = requests.post(provider_data['token_url'], data={
'client_id': provider_data['client_id'],
'client_secret': provider_data['client_secret'],
'code': request.args['code'],
'grant_type': 'authorization_code',
'redirect_uri': url_for('oauth2_callback', provider=provider,
_external=True),
}, headers={'Accept': 'application/json'})
if response.status_code != 200:
abort(401)
oauth2_token = response.json().get('access_token')
if not oauth2_token:
abort(401)
# use the access token to get the user's email address
response = requests.get(provider_data['userinfo']['url'], headers={
'Authorization': 'Bearer ' + oauth2_token,
'Accept': 'application/json',
})
if response.status_code != 200:
abort(401)
email = provider_data['userinfo']['email'](response.json())
# find or create the user in the database
user = db.session.scalar(db.select(User).where(User.email == email))
if user is None:
#user = User(email=email, username=email.split('@')[0])
user = User(email=email)
db.session.add(user)
db.session.commit()
# log the user in
login_user(user)
return redirect(url_for('index'))
@auth.before_app_request @auth.before_app_request
def before_request(): def before_request():

View file

@ -20,3 +20,32 @@ class Config(object):
MAIL_USE_TLS = f"{os.getenv('MAIL_USE_TLS')}" MAIL_USE_TLS = f"{os.getenv('MAIL_USE_TLS')}"
ADMIN_MAIL = f"{os.getenv('ADMIN_MAIL')}" ADMIN_MAIL = f"{os.getenv('ADMIN_MAIL')}"
ITEMS_PER_PAGE = f"{os.getenv('ITEMS_PER_PAGE')}" ITEMS_PER_PAGE = f"{os.getenv('ITEMS_PER_PAGE')}"
OAUTH2_PROVIDERS = {
# Google OAuth 2.0 documentation:
# https://developers.google.com/identity/protocols/oauth2/web-server#httprest
'google': {
'client_id': os.environ.get('GOOGLE_CLIENT_ID'),
'client_secret': os.environ.get('GOOGLE_CLIENT_SECRET'),
'authorize_url': 'https://accounts.google.com/o/oauth2/auth',
'token_url': 'https://accounts.google.com/o/oauth2/token',
'userinfo': {
'url': 'https://www.googleapis.com/oauth2/v3/userinfo',
'email': lambda json: json['email'],
},
'scopes': ['https://www.googleapis.com/auth/userinfo.email'],
},
# GitHub OAuth 2.0 documentation:
# https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
'github': {
'client_id': os.environ.get('GITHUB_CLIENT_ID'),
'client_secret': os.environ.get('GITHUB_CLIENT_SECRET'),
'authorize_url': 'https://github.com/login/oauth/authorize',
'token_url': 'https://github.com/login/oauth/access_token',
'userinfo': {
'url': 'https://api.github.com/user/emails',
'email': lambda json: json[0]['email'],
},
'scopes': ['user:email'],
}
}

View file

@ -4,7 +4,7 @@
{% import "bootstrap/fixes.html" as fixes %} {% import "bootstrap/fixes.html" as fixes %}
{% import "bootstrap/utils.html" as util %} {% import "bootstrap/utils.html" as util %}
{% block title %}Cloud Builder - Datapoint.bg{% endblock %} {% block title %}ForestNet{% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}

View file

@ -39,13 +39,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#newsmenu').load('https://www.datapoint.bg/news/latest'); $('#newsmenu').load('https://forest.deflax.net/news/latest');
});
</script>
<script type="text/javascript">
$(document).ready(function() {
$('#livesupport').load('https://www.datapoint.bg/kiwi/');
}); });
</script> </script>
{% endblock %} {% endblock %}
@ -75,6 +69,8 @@
<div class="panel-body"> <div class="panel-body">
<br /><br /><br /> <br /><br /><br />
{% if not current_user.is_authenticated %}
<div class="form"> <div class="form">
<form class="register-form" actoion="{{ url_for('auth.register') }}" method="POST"> <form class="register-form" actoion="{{ url_for('auth.register') }}" method="POST">
<input type="text" placeholder="name"/> <input type="text" placeholder="name"/>
@ -91,6 +87,7 @@
<p class="message">Not registered? <a href="#">Create an account</a></p> <p class="message">Not registered? <a href="#">Create an account</a></p>
</form> </form>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View file

@ -14,6 +14,11 @@ POSTGRES_DB=forest_prod
PGADMIN_DEFAULT_EMAIL=mail@example.com PGADMIN_DEFAULT_EMAIL=mail@example.com
PGADMIN_DEFAULT_PASSWORD=hackme PGADMIN_DEFAULT_PASSWORD=hackme
GOOGLE_CLIENT_ID=changeme
GOOGLE_CLIENT_SECRET=changeme
GITHUB_CLIENT_ID=changeme
GITHUB_CLIENT_SECRET=changeme
MAIL_SENDER=mail@example.com MAIL_SENDER=mail@example.com
MAIL_SUBJECT_PREFIX=ForestNet MAIL_SUBJECT_PREFIX=ForestNet
MAIL_SERVER=smtp.gmail.com MAIL_SERVER=smtp.gmail.com