From bb6840ab32d39240442f32c89ac3c4d0722d8372 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 4 Jan 2019 13:00:38 -0800 Subject: use .env for all config (and document it) --- python/fatcat_web/web_config.py | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 python/fatcat_web/web_config.py (limited to 'python/fatcat_web/web_config.py') diff --git a/python/fatcat_web/web_config.py b/python/fatcat_web/web_config.py new file mode 100644 index 00000000..5713738c --- /dev/null +++ b/python/fatcat_web/web_config.py @@ -0,0 +1,54 @@ + +""" +Default configuration for fatcat web interface (Flask application). + +In production, we currently reconfigure these values using environment +variables, not by (eg) deploying a variant copy of this file. + +This config is *only* for the web interface, *not* for any of the workers or +import scripts. +""" + +import os +import raven +import subprocess + +basedir = os.path.abspath(os.path.dirname(__file__)) + +class Config(object): + GIT_REVISION = subprocess.check_output(["git", "describe", "--always"]).strip() + + # This is, effectively, the QA/PROD flag + FATCAT_DOMAIN = os.environ.get("FATCAT_DOMAIN", default="qa.fatcat.wiki") + FATCAT_API_AUTH_TOKEN = os.environ.get("FATCAT_API_AUTH_TOKEN", default=None) + FATCAT_API_HOST = os.environ.get("FATCAT_API_HOST", default="https://{}/v0".format(FATCAT_DOMAIN)) + + # can set this to https://search.fatcat.wiki for some experimentation + ELASTICSEARCH_BACKEND = os.environ.get("ELASTICSEARCH_BACKEND", default="http://localhost:9200") + ELASTICSEARCH_INDEX = os.environ.get("ELASTICSEARCH_INDEX", default="fatcat") + + # for flask things, like session cookies + FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", default=None) + SECRET_KEY = FLASK_SECRET_KEY + + GITLAB_CLIENT_ID = os.environ.get("GITLAB_CLIENT_ID", default="bogus") + GITLAB_CLIENT_SECRET = os.environ.get("GITLAB_CLIENT_SECRET", default="bogus") + + try: + GIT_RELEASE = raven.fetch_git_sha('..') + except Exception as e: + print("WARNING: couldn't set sentry git release automatically: " + str(e)) + GIT_RELEASE = None + + SENTRY_CONFIG = { + #'include_paths': ['fatcat_web', 'fatcat_client', 'fatcat_tools'], + 'enable-threads': True, # for uWSGI + 'release': GIT_RELEASE, + 'tags': { + 'fatcat_domain': FATCAT_DOMAIN, + }, + } + + # "Even more verbose" debug options + #SQLALCHEMY_ECHO = True + #DEBUG = True -- cgit v1.2.3 From 6eeead67f1d9af4ff2fc3c6c1188bc372e7d05a0 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 4 Jan 2019 17:59:59 -0800 Subject: one-month default session; lock down cookies --- python/fatcat_web/auth.py | 2 ++ python/fatcat_web/web_config.py | 6 ++++++ 2 files changed, 8 insertions(+) (limited to 'python/fatcat_web/web_config.py') diff --git a/python/fatcat_web/auth.py b/python/fatcat_web/auth.py index 0bdb564f..8b57a8c0 100644 --- a/python/fatcat_web/auth.py +++ b/python/fatcat_web/auth.py @@ -28,6 +28,7 @@ def handle_token_login(token): abort(400) # fetch editor info editor = api.get_editor(editor_id) + session.permanent = True session['api_token'] = token session['editor'] = editor.to_dict() login_user(load_user(editor.editor_id)) @@ -64,6 +65,7 @@ def handle_oauth(remote, token, user_info): flash("Welcome back!") # write token and username to session + session.permanent = True session['api_token'] = api_token session['editor'] = editor.to_dict() diff --git a/python/fatcat_web/web_config.py b/python/fatcat_web/web_config.py index 5713738c..85134762 100644 --- a/python/fatcat_web/web_config.py +++ b/python/fatcat_web/web_config.py @@ -34,6 +34,12 @@ class Config(object): GITLAB_CLIENT_ID = os.environ.get("GITLAB_CLIENT_ID", default="bogus") GITLAB_CLIENT_SECRET = os.environ.get("GITLAB_CLIENT_SECRET", default="bogus") + # protect cookies (which include API tokens) + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SECURE = True + SESSION_COOKIE_SAMESITE = 'Lax' + PERMANENT_SESSION_LIFETIME = 2678400 # 31 days, in seconds + try: GIT_RELEASE = raven.fetch_git_sha('..') except Exception as e: -- cgit v1.2.3 From 5d5a5648cb480e05c4253c954c71094c7251b65a Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Mon, 7 Jan 2019 17:43:34 -0800 Subject: basic/crude IA login --- python/env.example | 2 + python/fatcat_web/auth.py | 57 ++++++++++++++++++++++++-- python/fatcat_web/routes.py | 11 ++++- python/fatcat_web/templates/auth_ia_login.html | 31 ++++++++++++++ python/fatcat_web/templates/auth_login.html | 1 + python/fatcat_web/templates/base.html | 2 +- python/fatcat_web/web_config.py | 8 +++- 7 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 python/fatcat_web/templates/auth_ia_login.html (limited to 'python/fatcat_web/web_config.py') diff --git a/python/env.example b/python/env.example index c139df07..c13c6246 100644 --- a/python/env.example +++ b/python/env.example @@ -5,4 +5,6 @@ ELASTICSEARCH_BACKEND="http://localhost:9200" ELASTICSEARCH_INDEX="fatcat" GITLAB_CLIENT_ID="" GITLAB_CLIENT_SECRET="" +IA_XAUTH_CLIENT_ID="" +IA_XAUTH_CLIENT_SECRET="" SENTRY_DSN="" diff --git a/python/fatcat_web/auth.py b/python/fatcat_web/auth.py index 8b57a8c0..8035cbe5 100644 --- a/python/fatcat_web/auth.py +++ b/python/fatcat_web/auth.py @@ -1,16 +1,19 @@ +from collections import namedtuple +import requests +import pymacaroons from flask import Flask, render_template, send_from_directory, request, \ url_for, abort, g, redirect, jsonify, session, flash from fatcat_web import login_manager, api, priv_api, Config from flask_login import logout_user, login_user, UserMixin -import pymacaroons import fatcat_client def handle_logout(): logout_user() - for k in ('editor', 'token'): + for k in ('editor', 'api_token'): if k in session: session.pop(k) + session.clear() def handle_token_login(token): try: @@ -73,14 +76,59 @@ def handle_oauth(remote, token, user_info): login_user(load_user(editor.editor_id)) return redirect("/auth/account") - raise some_error + # XXX: what should this actually be? + raise Exception("didn't receive OAuth user_info") + +def handle_ia_xauth(email, password): + resp = requests.post(Config.IA_XAUTH_URI, + params={'op': 'authenticate'}, + json={ + 'version': '1', + 'email': email, + 'password': password, + 'access': Config.IA_XAUTH_CLIENT_ID, + 'secret': Config.IA_XAUTH_CLIENT_SECRET, + }) + if resp.status_code == 401 or (not resp.json().get('success')): + flash("Internet Archive email/password didn't match: {}".format(resp.json()['values']['reason'])) + return render_template('auth_ia_login.html', email=email), resp.status_code + elif resp.status_code != 200: + flash("Internet Archive login failed (internal error?)") + # TODO: log.warn + print("IA XAuth fail: {}".format(resp.content)) + return render_template('auth_ia_login.html', email=email), resp.status_code + # Successful login; now fetch info... + resp = requests.post(Config.IA_XAUTH_URI, + params={'op': 'info'}, + json={ + 'version': '1', + 'email': email, + 'access': Config.IA_XAUTH_CLIENT_ID, + 'secret': Config.IA_XAUTH_CLIENT_SECRET, + }) + if resp.status_code != 200: + flash("Internet Archive login failed (internal error?)") + # TODO: log.warn + print("IA XAuth fail: {}".format(resp.content)) + return render_template('auth_ia_login.html', email=email), resp.status_code + ia_info = resp.json()['values'] + + # and pass off "as if" we did OAuth successfully + FakeOAuthRemote = namedtuple('FakeOAuthRemote', ['name', 'OAUTH_CONFIG']) + remote = FakeOAuthRemote(name='archive', OAUTH_CONFIG={'api_base_url': Config.IA_XAUTH_URI}) + oauth_info = { + 'preferred_username': ia_info['screenname'], + 'iss': Config.IA_XAUTH_URI, + 'sub': ia_info['itemname'], + } + return handle_oauth(remote, None, oauth_info) @login_manager.user_loader def load_user(editor_id): # looks for extra info in session, and updates the user object with that. # If session isn't loaded/valid, should return None - if not 'editor' in session or not 'api_token' in session: + if (not session.get('editor')) or (not session.get('api_token')): return None editor = session['editor'] token = session['api_token'] @@ -90,3 +138,4 @@ def load_user(editor_id): user.username = editor['username'] user.token = token return user + diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py index ebf7e88a..789d7bed 100644 --- a/python/fatcat_web/routes.py +++ b/python/fatcat_web/routes.py @@ -5,7 +5,7 @@ from flask import Flask, render_template, send_from_directory, request, \ url_for, abort, g, redirect, jsonify, session, flash from flask_login import login_required from fatcat_web import app, api, auth_api -from fatcat_web.auth import handle_token_login, handle_logout, load_user +from fatcat_web.auth import handle_token_login, handle_logout, load_user, handle_ia_xauth from fatcat_client.rest import ApiException from fatcat_web.search import do_search @@ -381,6 +381,14 @@ def login(): # show the user a list of login options return render_template('auth_login.html') +@app.route('/auth/ia/login', methods=['GET', 'POST']) +def ia_xauth_login(): + if 'email' in request.form: + # if a login attempt... + return handle_ia_xauth(request.form.get('email'), request.form.get('password')) + # else show form + return render_template('auth_ia_login.html') + @app.route('/auth/token_login', methods=['GET', 'POST']) def token_login(): # show the user a list of login options @@ -409,7 +417,6 @@ def change_username(): @app.route('/auth/logout') def logout(): - # TODO: clear extra session info handle_logout() return render_template('auth_logout.html') diff --git a/python/fatcat_web/templates/auth_ia_login.html b/python/fatcat_web/templates/auth_ia_login.html new file mode 100644 index 00000000..ebf08021 --- /dev/null +++ b/python/fatcat_web/templates/auth_ia_login.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% block body %} +

Login with Internet Archive account

+ +

Warning: still experimental! + +
+
+
+ +{% if current_user.is_authenticated %} +

+
You are already logged in!
+

You should logout first. Re-authenticating would be undefined behavior. +

+{% else %} +
+
+
+ +
+
+ + +
+
+ +
+{% endif %} + +{% endblock %} diff --git a/python/fatcat_web/templates/auth_login.html b/python/fatcat_web/templates/auth_login.html index 98b1c7c4..9ccae816 100644 --- a/python/fatcat_web/templates/auth_login.html +++ b/python/fatcat_web/templates/auth_login.html @@ -12,6 +12,7 @@

Other options...

{% endblock %} diff --git a/python/fatcat_web/templates/base.html b/python/fatcat_web/templates/base.html index e3824213..3b324cba 100644 --- a/python/fatcat_web/templates/base.html +++ b/python/fatcat_web/templates/base.html @@ -55,7 +55,7 @@ {% if messages %}
{# Needs more javascript: #} -
Now Hear This!
+
Now Hear This...
    {% for message in messages %}
  • {{ message }} diff --git a/python/fatcat_web/web_config.py b/python/fatcat_web/web_config.py index 85134762..0ae43a3a 100644 --- a/python/fatcat_web/web_config.py +++ b/python/fatcat_web/web_config.py @@ -31,8 +31,12 @@ class Config(object): FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", default=None) SECRET_KEY = FLASK_SECRET_KEY - GITLAB_CLIENT_ID = os.environ.get("GITLAB_CLIENT_ID", default="bogus") - GITLAB_CLIENT_SECRET = os.environ.get("GITLAB_CLIENT_SECRET", default="bogus") + GITLAB_CLIENT_ID = os.environ.get("GITLAB_CLIENT_ID", default=None) + GITLAB_CLIENT_SECRET = os.environ.get("GITLAB_CLIENT_SECRET", default=None) + + IA_XAUTH_URI = "https://archive.org/services/xauthn/" + IA_XAUTH_CLIENT_ID = os.environ.get("IA_XAUTH_CLIENT_ID", default=None) + IA_XAUTH_CLIENT_SECRET = os.environ.get("IA_XAUTH_CLIENT_SECRET", default=None) # protect cookies (which include API tokens) SESSION_COOKIE_HTTPONLY = True -- cgit v1.2.3 From 0e344762101e9cb1f57a139c726fd50f2364ad51 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Tue, 8 Jan 2019 13:57:09 -0800 Subject: decode GIT_REVISION earlier --- python/fatcat_web/templates/base.html | 2 +- python/fatcat_web/web_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'python/fatcat_web/web_config.py') diff --git a/python/fatcat_web/templates/base.html b/python/fatcat_web/templates/base.html index 3b324cba..cce841e5 100644 --- a/python/fatcat_web/templates/base.html +++ b/python/fatcat_web/templates/base.html @@ -80,7 +80,7 @@ Sources Status Bulk Exports - Source Code ({{ config.GIT_REVISION.decode() }}) + Source Code ({{ config.GIT_REVISION }})
diff --git a/python/fatcat_web/web_config.py b/python/fatcat_web/web_config.py index 0ae43a3a..cbe519b0 100644 --- a/python/fatcat_web/web_config.py +++ b/python/fatcat_web/web_config.py @@ -16,7 +16,7 @@ import subprocess basedir = os.path.abspath(os.path.dirname(__file__)) class Config(object): - GIT_REVISION = subprocess.check_output(["git", "describe", "--always"]).strip() + GIT_REVISION = subprocess.check_output(["git", "describe", "--always"]).strip().decode('utf-8') # This is, effectively, the QA/PROD flag FATCAT_DOMAIN = os.environ.get("FATCAT_DOMAIN", default="qa.fatcat.wiki") -- cgit v1.2.3