diff --git a/README.md b/README.md index 23129f6..6ebca7d 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Put the following code into the `post.hbs` of your ghost theme and replace `{{gh ``` Example file [ghost-integration/post.hbs](ghost-integration/post.hbs) -# Author address setup +## Author address setup You can set `AUTHOR_ADDRESSES=true` in your `.env` file to allow authors to get paid individually. If so, the authors can paste in their current IOTA address in the `location` field in their ghost admin panel. When an article is requested, the ghost API is fetched to get the latest address. If there is none, there is a fallback to the last address used or if this is not the case, the default address specified in the `.env` file is used. @@ -133,3 +133,7 @@ As an IOTA address is quite ugly in the location field of the author, you can mo {{/if}} ``` + +## Author address setup +You can set `ADMIN_PANEL=true` in your `.env` file to enable the admin panel on the `admin` path. +The admin panel is secured using basic authentication which credential are specified in the `.env` file as well. diff --git a/admin/__init__.py b/admin/__init__.py index 1270182..bd4b0dc 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -1,4 +1,5 @@ -from flask_admin import Admin +from flask_admin import Admin, AdminIndexView, BaseView, expose +from flask_basicauth import BasicAuth from flask_admin.contrib.sqla import ModelView from database.db import db from database.models.access import Access @@ -6,14 +7,50 @@ from database.models.sessions import Session from database.models.slugs import Slug -admin = Admin() -class PkModelView(ModelView): - column_display_pk = True +class AuthenticatedAdminView(AdminIndexView): + + def is_accessible(self): + if not auth.authenticate(): + return False + else: + return True + + def inaccessible_callback(self, name, **kwargs): + return auth.challenge() + + super(AdminIndexView) + + +class AuthenticatedModelView(ModelView): + def is_accessible(self): + if not auth.authenticate(): + return False + else: + return True + + def inaccessible_callback(self, name, **kwargs): + return auth.challenge() + super(ModelView) -admin.add_view(PkModelView(Access, db.session)) -admin.add_view(PkModelView(Author, db.session)) -admin.add_view(PkModelView(Session, db.session)) -admin.add_view(PkModelView(Slug, db.session)) +class HomeView(BaseView): + @expose('/') + def index(self): + arg1 = 'Hello' + return self.render('admin/myhome.html', arg1=arg1) + + +class PkModelView(AuthenticatedModelView): + column_display_pk = True + + super(AuthenticatedModelView) + + +admin = Admin(index_view=AuthenticatedAdminView(), template_mode='bootstrap4') +auth = BasicAuth() +admin.add_view(PkModelView(Access, db.session, name='Access', category='Database')) +admin.add_view(PkModelView(Author, db.session, name='Authors', category='Database')) +admin.add_view(PkModelView(Session, db.session, name='Sessions', category='Database')) +admin.add_view(PkModelView(Slug, db.session, name='Posts', category='Database')) diff --git a/app.py b/app.py index 3338f20..355cebc 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,12 @@ import secrets from utils.hash import hash_user_token from services.ghost_api import get_post, get_post_payment -from config import SECRET_KEY, DATABASE_LOCATION, SESSION_LIFETIME, URL, ADMIN_PANEL +from config import (SECRET_KEY, + DATABASE_LOCATION, + SESSION_LIFETIME, + URL, ADMIN_PANEL, + ADMIN_USER, + ADMIN_PW) import os from services.iota import Listener from datetime import datetime, timedelta @@ -14,7 +19,7 @@ send_from_directory, redirect) from database.db import db -from admin import admin +from admin import admin, auth from database.operations import (check_slug, get_access, get_slug_data, @@ -31,6 +36,10 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['BASIC_AUTH_USERNAME'] = ADMIN_USER + +app.config['BASIC_AUTH_PASSWORD'] = ADMIN_PW + # create web socket for async communication socketio = SocketIO(app, async_mode='threading', cors_allowed_origins="*") @@ -137,6 +146,7 @@ def await_payment(data): if ADMIN_PANEL: admin.init_app(app) + auth.init_app(app) socketio.start_background_task(iota_listener.start, app) socketio.run(app, host='0.0.0.0') diff --git a/config/__init__.py b/config/__init__.py index 5a2ad4f..41b95b2 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -21,4 +21,8 @@ GHOST_ADMIN_KEY = getenv('GHOST_ADMIN_KEY') -ADMIN_PANEL = getenv('ADMIN_PANEL') == 'true' \ No newline at end of file +ADMIN_PANEL = getenv('ADMIN_PANEL') == 'true' + +ADMIN_USER = getenv('ADMIN_USER') + +ADMIN_PW = getenv('ADMIN_PW') \ No newline at end of file diff --git a/database/models/authors.py b/database/models/authors.py index ec437cc..ec1e267 100644 --- a/database/models/authors.py +++ b/database/models/authors.py @@ -3,4 +3,5 @@ class Author(db.Model): id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(64)) iota_address = db.Column(db.String(64), default=DEFAULT_IOTA_ADDRESS) diff --git a/database/operations.py b/database/operations.py index 577cda7..482d7f1 100644 --- a/database/operations.py +++ b/database/operations.py @@ -38,7 +38,7 @@ def check_slug(slug, iota_listener): # add author if not existant if AUTHOR_ADDRESSES and not __exists(Author, post_data['primary_author']['id']): - add_author(post_data['primary_author']['id'], post_data['primary_author']['location']) + add_author(post_data['primary_author']['id'], post_data['primary_author']['name'], post_data['primary_author']['location']) iota_listener.add_listening_address(post_data['primary_author']['location']) @@ -114,9 +114,9 @@ def add_access(user_token_hash, exp_date=None): db.session.commit() -def add_author(author_id, iota_address): +def add_author(author_id, name, iota_address): - author = Author(id=author_id, iota_address=iota_address) + author = Author(id=author_id, name=name, iota_address=iota_address) db.session.add(author) db.session.commit() diff --git a/example.env b/example.env index 39aae4b..529ca30 100644 --- a/example.env +++ b/example.env @@ -25,3 +25,7 @@ AUTHOR_ADDRESSES=false # allow admin access via flask-admin ADMIN_PANEL=false + +ADMIN_USER=admin + +ADMIN_PW=secret_password \ No newline at end of file diff --git a/services/iota.py b/services/iota.py index 5ac4098..8be4e40 100644 --- a/services/iota.py +++ b/services/iota.py @@ -52,7 +52,8 @@ def on_mqtt_event(self, event): def start(self, app): - self.client.subscribe_topics(get_iota_listening_addresses(), self.on_mqtt_event) + with app.app_context(): + self.client.subscribe_topics(get_iota_listening_addresses(), self.on_mqtt_event) self.mqtt_worker(app) diff --git a/templates/admin/index.html b/templates/admin/index.html new file mode 100644 index 0000000..a180d9d --- /dev/null +++ b/templates/admin/index.html @@ -0,0 +1,14 @@ +{% extends 'admin/master.html' %} + +{% block body %} +{{ super() }} +
+ +

Ghost IOTA Pay

+

Admin Panel

+

Pay per content gateway for Ghost Blogs

+
+

+
+
+{% endblock body %} \ No newline at end of file