diff --git a/Dockerfile b/Dockerfile
index fd45ad1a..43cf39c5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.8 as backend-dev
+FROM python:3.10 as backend-dev
ENV PYTHONUNBUFFERED=1
RUN useradd -m -d /opt/spacedock -s /bin/bash spacedock
RUN pip3 install --upgrade pip setuptools wheel pip-licenses
diff --git a/KerbalStuff/app.py b/KerbalStuff/app.py
index 6de11f4a..4b91624f 100644
--- a/KerbalStuff/app.py
+++ b/KerbalStuff/app.py
@@ -14,11 +14,10 @@
import werkzeug.wrappers
from flask import Flask, render_template, g, url_for, Response, request
from flask_login import LoginManager, current_user
-from flaskext.markdown import Markdown
+from markupsafe import Markup
from werkzeug.exceptions import HTTPException, InternalServerError, NotFound
from flask.typing import ResponseReturnValue
from jinja2 import ChainableUndefined
-from pymdownx.emoji import gemoji, to_alt
from .blueprints.accounts import accounts
from .blueprints.admin import admin
@@ -26,18 +25,16 @@
from .blueprints.api import api
from .blueprints.blog import blog, get_all_announcement_posts, get_non_member_announcement_posts
from .blueprints.lists import lists
-from .blueprints.login_oauth import list_defined_oauths, login_oauth
from .blueprints.mods import mods
from .blueprints.profile import profiles
from .middleware.session_interface import OnlyLoggedInSessionInterface
from .celery import update_from_github
-from .common import first_paragraphs, many_paragraphs, json_output, jsonify_exception, dumb_object, sanitize_text
+from .common import first_paragraphs, many_paragraphs, json_output, jsonify_exception, dumb_object, sanitize_text, markdown_renderer
from .config import _cfg, _cfgb, _cfgd, _cfgi, site_logger
from .custom_json import CustomJSONEncoder
from .database import db
from .helpers import is_admin, following_mod
-from .kerbdown import KerbDown
-from .objects import User, BlogPost
+from .objects import User
app = Flask(__name__, template_folder='../templates')
# https://flask.palletsprojects.com/en/1.1.x/security/#set-cookie-options
@@ -59,15 +56,9 @@
app.jinja_env.filters['bleach'] = sanitize_text
app.jinja_env.auto_reload = app.debug
app.secret_key = _cfg("secret-key")
-app.json_encoder = CustomJSONEncoder
+app.json_encoder = CustomJSONEncoder # type: ignore[attr-defined]
app.session_interface = OnlyLoggedInSessionInterface()
-Markdown(app, extensions=[KerbDown(), 'fenced_code', 'pymdownx.emoji'],
- extension_configs={'pymdownx.emoji': {
- # GitHub's emojis
- 'emoji_index': gemoji,
- # Unicode output
- 'emoji_generator': to_alt
- }})
+app.jinja_env.filters['markdown'] = lambda md: Markup(markdown_renderer.convert(md))
login_manager = LoginManager(app)
prof_dir = _cfg('profile-dir')
@@ -95,7 +86,6 @@ def load_user(username: str) -> User:
app.register_blueprint(profiles)
app.register_blueprint(accounts)
-app.register_blueprint(login_oauth)
app.register_blueprint(anonymous)
app.register_blueprint(blog)
app.register_blueprint(admin)
@@ -320,7 +310,6 @@ def inject() -> Dict[str, Any]:
'any': any,
'following_mod': following_mod,
'admin': is_admin(),
- 'oauth_providers': list_defined_oauths(),
'dumb_object': dumb_object,
'first_visit': first_visit,
'request': request,
diff --git a/KerbalStuff/blueprints/login_oauth.py b/KerbalStuff/blueprints/login_oauth.py
deleted file mode 100644
index 6b0d4703..00000000
--- a/KerbalStuff/blueprints/login_oauth.py
+++ /dev/null
@@ -1,303 +0,0 @@
-import binascii
-import os
-from collections import OrderedDict
-from typing import List, Dict, Optional, Union, Tuple, Any
-import werkzeug.wrappers
-
-from flask import Blueprint, render_template, request, redirect, session, jsonify, url_for, \
- current_app
-from flask_login import current_user, login_user
-from flask_oauthlib.client import OAuth, OAuthRemoteApp
-
-from .accounts import check_username_for_registration, \
- check_email_for_registration
-from ..config import _cfg
-from ..database import db
-from ..email import send_confirmation
-from ..objects import User, UserAuth
-
-login_oauth = Blueprint('login_oauth', __name__)
-
-
-# Python doesn't like OrderedDict with brackets, but mypy is fine with it
-DEFINED_OAUTHS: Optional['OrderedDict[str, Dict[str, Any]]'] = None
-
-
-def list_connected_oauths(user: User) -> List[str]:
- return [a.provider for a in UserAuth.query.filter(UserAuth.user_id == user.id)]
-
-
-def list_defined_oauths() -> 'OrderedDict[str, Dict[str, Any]]':
- global DEFINED_OAUTHS
- if DEFINED_OAUTHS is not None:
- return DEFINED_OAUTHS
- master_list = OrderedDict()
- master_list['github'] = {
- 'full_name': 'GitHub',
- 'icon': 'github',
- }
- master_list['google'] = {
- 'full_name': 'Google',
- 'icon': 'google',
- }
- master_list['facebook'] = {
- 'full_name': 'Facebook',
- 'icon': 'facebook-official',
- }
- for p in list(master_list.keys()):
- if not is_oauth_provider_configured(p):
- del master_list[p]
- DEFINED_OAUTHS = master_list
- return DEFINED_OAUTHS
-
-
-def is_oauth_provider_configured(provider: str) -> bool:
- if provider == 'github':
- return bool(_cfg('gh-oauth-id')) and bool(_cfg('gh-oauth-secret'))
- if provider == 'google':
- return (bool(_cfg('google-oauth-id')) and
- bool(_cfg('google-oauth-secret')))
- return False
-
-
-def get_github_oath() -> Tuple[str, OAuthRemoteApp]:
- github = get_oauth_provider('github')
- resp = github.authorized_response()
- if resp is None:
- raise Exception(
- f"Access denied: reason={request.args['error']} error={request.args['error_description']}")
- session['github_token'] = (resp['access_token'], '')
- gh_info = github.get('user').data
- return gh_info['login'], github
-
-
-def _connect_with_oauth_finalize(remote_user: str, provider: str) -> Union[str, werkzeug.wrappers.Response]:
- if not current_user:
- return 'Trying to associate an account, but not logged in?'
- auth = UserAuth.query.filter(UserAuth.provider == provider,
- UserAuth.remote_user == remote_user).first()
- if auth:
- if auth.user_id == current_user.id:
- # You're already set up.
- return redirect('/profile/%s/edit' % current_user.username)
- # This account is already connected with some user.
- full_name = list_defined_oauths()[provider]['full_name']
- return 'Your %s account is already connected to a SpaceDock account.' % full_name
- auth = UserAuth(user_id=current_user.id,
- remote_user=remote_user,
- provider=provider)
- db.add(auth)
- db.flush() # So that /profile will display currectly
- return redirect('/profile/%s/edit' % current_user.username)
-
-
-@login_oauth.route("/login-oauth", methods=['GET', 'POST'])
-def login_with_oauth() -> Union[str, werkzeug.wrappers.Response]:
- if request.method == 'GET':
- return redirect('/login')
- provider = request.form.get('provider', '')
- if not is_oauth_provider_configured(provider):
- return 'This install is not configured for login with %s' % provider
- oauth = get_oauth_provider(provider)
- callback = "{}://{}{}".format(_cfg("protocol"), _cfg("domain"),
- url_for('.login_with_oauth_authorized_' + provider))
- return oauth.authorize(callback=callback)
-
-
-@login_oauth.route("/connect-oauth", methods=['POST'])
-def connect_with_oauth() -> Union[str, werkzeug.wrappers.Response]:
- provider = request.form.get('provider', '')
- if not is_oauth_provider_configured(provider):
- return 'This install is not configured for login with %s' % provider
- oauth = get_oauth_provider(provider)
- callback = "{}://{}{}".format(_cfg("protocol"), _cfg("domain"),
- url_for('.connect_with_oauth_authorized_' + provider))
- return oauth.authorize(callback=callback)
-
-
-@login_oauth.route("/disconnect-oauth", methods=['POST'])
-def disconnect_oauth() -> werkzeug.wrappers.Response:
- provider = request.form.get('provider')
- assert provider in list_defined_oauths() # This is a quick and dirty form of sanitation.
- auths = UserAuth.query.filter(UserAuth.provider == provider,
- UserAuth.user_id == current_user.id).all()
- for auth in auths:
- db.delete(auth)
- db.flush() # So that /profile will display currectly
- return redirect('/profile/%s/edit' % current_user.username)
-
-
-@login_oauth.route("/oauth/github/connect")
-def connect_with_oauth_authorized_github() -> Union[str, werkzeug.wrappers.Response]:
- gh_user, _ = get_github_oath()
- return _connect_with_oauth_finalize(gh_user, 'github')
-
-
-@login_oauth.route("/oauth/google/connect")
-def connect_with_oauth_authorized_google() -> Union[str, werkzeug.wrappers.Response]:
- if 'code' not in request.args:
- # Got here in some strange scenario.
- return redirect('/')
- google = get_oauth_provider('google')
- resp = google.authorized_response()
- if resp is None:
- return 'Access denied: reason=%s error=%s' % (
- request.args['error'],
- request.args['error_description']
- )
- if 'error' in resp:
- return jsonify(resp)
- session['google_token'] = (resp['access_token'], '')
- google_info = google.get('userinfo')
- google_info = google_info.data
- google_user = google_info['id'] # This is a long number.
- return _connect_with_oauth_finalize(google_user, 'google')
-
-
-@login_oauth.route("/oauth/github/login")
-def login_with_oauth_authorized_github() -> Union[str, werkzeug.wrappers.Response]:
- gh_user, github = get_github_oath()
- auth = UserAuth.query.filter(
- UserAuth.provider == 'github',
- UserAuth.remote_user == gh_user).first()
- if auth:
- user = User.query.filter(User.id == auth.user_id).first()
- if user.confirmation:
- return redirect('/account-pending')
- login_user(user, remember=True)
- return redirect('/')
- else:
- emails = github.get('user/emails', [])
- emails = emails.data
- emails = [e['email'] for e in emails if e['primary']]
- if emails:
- email = emails[0]
- else:
- email = ''
- return render_register_with_oauth('github', gh_user, gh_user, email)
-
-
-@login_oauth.route("/oauth/google/login")
-def login_with_oauth_authorized_google() -> Union[str, werkzeug.wrappers.Response]:
- if 'code' not in request.args:
- # Got here in some strange scenario.
- return redirect('/')
- google = get_oauth_provider('google')
- resp = google.authorized_response()
-
- if resp is None:
- return 'Access denied: reason=%s error=%s' % (
- request.args['error'],
- request.args['error_description']
- )
- if 'error' in resp:
- return jsonify(resp.error)
- session['google_token'] = (resp['access_token'], '')
- google_info = google.get('userinfo')
- google_info = google_info.data
- google_user = google_info['id'] # This is a long number.
- auth = UserAuth.query.filter(
- UserAuth.provider == 'google',
- UserAuth.remote_user == google_user).first()
- if auth:
- user = User.query.filter(User.id == auth.user_id).first()
- if user.confirmation:
- return redirect('/account-pending')
- login_user(user, remember=True)
- return redirect('/')
- else:
- email = google_info['email']
- username = email[:email.find('@')]
- return render_register_with_oauth('google', google_user, username, email)
-
-
-@login_oauth.route("/register-oauth", methods=['POST'])
-def register_with_oauth_authorized() -> Union[str, werkzeug.wrappers.Response]:
- """
- This endpoint should be called after authorizing with oauth, by the user.
- """
- email = request.form.get('email', '')
- username = request.form.get('username', '')
- provider = request.form.get('provider', '')
- remote_user = request.form.get('remote_user', '')
- good = True
- if check_username_for_registration(username):
- good = False
- if check_email_for_registration(email):
- good = False
- if good:
- password = binascii.b2a_hex(os.urandom(99))
- user = User(username=username, email=email)
- user.set_password(str(password))
- user.create_confirmation()
- db.add(user)
- db.flush() # to get an ID.
- auth = UserAuth(user_id=user.id,
- remote_user=remote_user,
- provider=provider)
- db.add(auth)
- db.commit() # Commit before trying to email
- send_confirmation(user)
- return redirect("/account-pending")
- return render_register_with_oauth(provider, remote_user, username, email)
-
-
-def render_register_with_oauth(provider: str, remote_user: str, username: str, email: str) -> str:
- provider_info = list_defined_oauths()[provider]
- parameters = {
- 'email': email, 'username': username,
- 'provider': provider,
- 'provider_full_name': provider_info['full_name'],
- 'provider_icon': provider_info['icon'],
- 'remote_user': remote_user
- }
- error = check_username_for_registration(username)
- if error:
- parameters['usernameError'] = error
- error = check_email_for_registration(email)
- if error:
- parameters['emailError'] = error
- return render_template('register-oauth.html', **parameters)
-
-
-def get_oauth_provider(provider: str) -> OAuthRemoteApp:
- oauth = OAuth(current_app)
- if provider == 'github':
- github = oauth.remote_app(
- 'github',
- consumer_key=_cfg('gh-oauth-id'),
- consumer_secret=_cfg('gh-oauth-secret'),
- request_token_params={'scope': 'user:email'},
- base_url='https://api.github.com/',
- request_token_url=None,
- access_token_method='POST',
- access_token_url='https://github.com/login/oauth/access_token',
- authorize_url='https://github.com/login/oauth/authorize'
- )
-
- @github.tokengetter
- def get_github_oauth_token() -> str:
- return session.get('github_token', '')
-
- return github
-
- if provider == 'google':
- google = oauth.remote_app(
- 'google',
- consumer_key=_cfg('google-oauth-id'),
- consumer_secret=_cfg('google-oauth-secret'),
- request_token_params={'scope': 'email'},
- base_url='https://www.googleapis.com/oauth2/v1/',
- request_token_url=None,
- access_token_method='POST',
- access_token_url='https://accounts.google.com/o/oauth2/token',
- authorize_url='https://accounts.google.com/o/oauth2/auth',
- )
-
- @google.tokengetter
- def get_google_oauth_token() -> str:
- return session.get('google_token', '')
-
- return google
-
- raise Exception('This OAuth provider was not implemented: ' + provider)
diff --git a/KerbalStuff/blueprints/profile.py b/KerbalStuff/blueprints/profile.py
index 1cb66f60..89ccc8a1 100644
--- a/KerbalStuff/blueprints/profile.py
+++ b/KerbalStuff/blueprints/profile.py
@@ -6,7 +6,6 @@
from flask import Blueprint, render_template, abort, request, redirect
from flask_login import current_user
-from .login_oauth import list_connected_oauths, list_defined_oauths
from ..common import loginrequired, with_session, sendfile, TRUE_STR
from ..config import _cfg
from ..objects import User, Following
@@ -14,7 +13,7 @@
profiles = Blueprint('profile', __name__)
FORUM_PROFILE_URL_PATTERN = re.compile(
- r'^(?P
- Register
Have you forgotten your password?
Or maybe you want to create a new account?
If you run into any trouble, please get in touch.
- {% for provider in oauth_providers %} - {% set provider_icon = oauth_providers[provider].icon %} - {% set provider_full_name = oauth_providers[provider].full_name %} - - {% endfor %} diff --git a/templates/profile.html b/templates/profile.html index d78be16d..66d23b2b 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -291,91 +291,7 @@- Connecting your account on another service allows you to log in with that service. -
--
- -