authenticate first, ask for micropub authorization later
This commit is contained in:
parent
254b0ac5b6
commit
c70f527ec7
11 changed files with 215 additions and 62 deletions
|
@ -1,18 +1 @@
|
|||
Flask==0.10.1
|
||||
Flask-Login==0.2.11
|
||||
Flask_Micropub==0.1.4
|
||||
Flask-SQLAlchemy==2.0
|
||||
Jinja2==2.7.3
|
||||
MarkupSafe==0.23
|
||||
SQLAlchemy==0.9.8
|
||||
Werkzeug==0.9.6
|
||||
beautifulsoup4==4.3.2
|
||||
bleach==1.4.1
|
||||
celery==3.1.17
|
||||
feedparser==5.1.3
|
||||
html5lib==0.999
|
||||
itsdangerous==0.24
|
||||
-e git+git@github.com:kylewm/mf2py.git#egg=mf2py
|
||||
mf2util==0.1.4
|
||||
redis==2.10.3
|
||||
requests==2.5.1
|
||||
-e .
|
||||
|
|
15
setup.py
Normal file
15
setup.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(name='Woodwind',
|
||||
version='1.0.0',
|
||||
description='Stream-style indieweb reader',
|
||||
author='Kyle Mahan',
|
||||
author_email='kyle@kylewm.com',
|
||||
url='https://indiewebcamp.com/Woodwind',
|
||||
packages=['woodwind'],
|
||||
install_requires=[
|
||||
'Flask', 'Flask-Login', 'Flask-Micropub', 'Flask-SQLAlchemy',
|
||||
'beautifulsoup4', 'bleach', 'celery', 'feedparser', 'html5lib',
|
||||
'mf2py', 'mf2util', 'redis', 'requests'])
|
|
@ -12,7 +12,6 @@ bleach.ALLOWED_ATTRIBUTES.update({
|
|||
})
|
||||
|
||||
|
||||
|
||||
users_to_feeds = db.Table(
|
||||
'users_to_feeds', db.Model.metadata,
|
||||
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), index=True),
|
||||
|
@ -21,10 +20,11 @@ users_to_feeds = db.Table(
|
|||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
url = db.Column(db.String(256))
|
||||
domain = db.Column(db.String(256))
|
||||
micropub_endpoint = db.Column(db.String(512))
|
||||
access_token = db.Column(db.String(512))
|
||||
|
||||
|
||||
# Flask-Login integration
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
|
|
@ -369,6 +369,15 @@ header, main {
|
|||
header {
|
||||
margin-bottom: 1em; }
|
||||
|
||||
ul#navigation {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: right; }
|
||||
ul#navigation li {
|
||||
display: inline-block;
|
||||
padding: 3px; }
|
||||
|
||||
.pager {
|
||||
text-align: center;
|
||||
margin: 1em 0; }
|
||||
|
@ -404,8 +413,19 @@ article {
|
|||
font-size: 1.2em;
|
||||
font-weight: bold; }
|
||||
|
||||
form textarea {
|
||||
width: 90%; }
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block; }
|
||||
|
||||
textarea, input[type="text"], input[type="url"] {
|
||||
width: 100%;
|
||||
margin: 0.25em 0; }
|
||||
|
||||
button {
|
||||
padding: 0.25em; }
|
||||
|
||||
form {
|
||||
margin: 1em 0; }
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
article header img {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": 3,
|
||||
"mappings": "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",
|
||||
"mappings": "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",
|
||||
"sources": ["normalize.scss","style.scss"],
|
||||
"names": [],
|
||||
"file": "style.css"
|
||||
|
|
|
@ -30,6 +30,18 @@ header {
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
ul#navigation {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: right;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.pager {
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
|
@ -44,7 +56,6 @@ header {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
article {
|
||||
margin-bottom: 2em;
|
||||
box-shadow: $box-shadow;
|
||||
|
@ -79,10 +90,22 @@ article {
|
|||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea, input[type="text"], input[type="url"] {
|
||||
width: 100%;
|
||||
margin: 0.25em 0 ;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
form {
|
||||
textarea {
|
||||
width: 90%;
|
||||
}
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
|
|
|
@ -5,23 +5,38 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Woodwind</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
|
||||
<script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1 class="h-x-app">Woodwind</h1>
|
||||
|
||||
{% if current_user.is_authenticated() %}
|
||||
<div class="logged-in">{{ current_user.domain }}
|
||||
(<a href="{{ url_for('.logout') }}">Logout</a>)
|
||||
</div>
|
||||
<ul id="navigation">
|
||||
<li>
|
||||
<a href="{{ url_for('.index') }}">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('.feeds') }}">Feeds</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('.settings') }}">Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ current_user.domain }}
|
||||
(<a href="{{ url_for('.logout') }}">Logout</a>)
|
||||
</li>
|
||||
</ul>
|
||||
<h1 class="h-x-app">Woodwind</h1>
|
||||
{% else %}
|
||||
<form action="{{ url_for('.login') }}" method="GET">
|
||||
<h1 class="h-x-app">Woodwind</h1>
|
||||
<form action="{{ url_for('.login') }}" method="POST">
|
||||
<input type="text" name="me" placeholder="mydomain.com" />
|
||||
<input type="hidden" name="next" placeholder="{{ request.path }}" />
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% block header %}{% endblock %}
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message | safe }}</div>
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
{% extends "base.jinja2" %}
|
||||
{% block head %}
|
||||
<script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
|
||||
<script src="{{url_for('static', filename='feed.js')}}"></script>
|
||||
{% endblock head %}
|
||||
|
||||
{% block header %}
|
||||
{% if current_user.is_authenticated() %}
|
||||
<form action="{{ url_for('.subscribe') }}" method="POST">
|
||||
<input type="url" id="origin" name="origin" placeholder="Feed URL" />
|
||||
<button type="submit" id="subscribe">Subscribe</button>
|
||||
<input type="url" id="origin" name="origin" placeholder="Subscribe to URL" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock header %}
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block header %}
|
||||
{% if current_user.is_authenticated() %}
|
||||
<form action="{{ url_for('.subscribe') }}" method="POST">
|
||||
<input type="url" id="origin" name="origin" placeholder="Subscribe to URL" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock header %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
{% for feed in feeds %}
|
||||
|
||||
<div>
|
||||
<article>
|
||||
<form style="display:inline"
|
||||
action="{{ url_for('.edit_feed') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<input type="text" name="name" style="width:25%" value="{{ feed.name }}"/>
|
||||
<input type="text" name="feed" style="width:25%" value="{{ feed.feed }}"/>
|
||||
<button type="submit">Save</button>
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" value="{{ feed.name }}"/>
|
||||
<label>URL</label>
|
||||
<input type="text" name="feed" value="{{ feed.feed }}"/>
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
<form style="display:inline;"
|
||||
action="{{ url_for('.update_feed') }}" method="GET">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<button type="submit">Update</button>
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
<form style="display:inline;"
|
||||
action="{{ url_for('.delete_feed') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<button type="submit">Delete</button>
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<button type="submit">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock body %}
|
||||
|
|
46
woodwind/templates/settings.jinja2
Normal file
46
woodwind/templates/settings.jinja2
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% extends "base.jinja2" %}
|
||||
{% block body %}
|
||||
<main>
|
||||
|
||||
<!-- reply via micropub -->
|
||||
|
||||
{% if current_user.micropub_endpoint or current_user.access_token %}
|
||||
<form>
|
||||
<label>Micropub Endpoint</label>
|
||||
<input type="text" value="{{ current_user.micropub_endpoint }}" readonly />
|
||||
|
||||
<label>Access Token</label>
|
||||
<input type="text" value="{{ current_user.access_token }}" readonly />
|
||||
</form>
|
||||
|
||||
<form action="{{ url_for('.authorize') }}" method="POST">
|
||||
<button type="submit">
|
||||
Reauthorize Micropub
|
||||
</button>
|
||||
<input type="hidden" name="next" value="{{ request.path }}" />
|
||||
</form>
|
||||
|
||||
<form action="{{ url_for('.deauthorize') }}" method="POST">
|
||||
<button type="submit">Revoke Credentials</button>
|
||||
<input type="hidden" name="next" value="{{ request.path }}" />
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<form action="{{ url_for('.authorize') }}" method="POST">
|
||||
<button type="submit">
|
||||
Authorize Micropub
|
||||
</button>
|
||||
<input type="hidden" name="next" value="{{ request.path }}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<!-- reply via indie-config -->
|
||||
|
||||
|
||||
|
||||
<!-- configure endpoints manually -->
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
{% endblock body %}
|
|
@ -36,9 +36,17 @@ def install():
|
|||
|
||||
|
||||
@views.route('/feeds')
|
||||
@flask_login.login_required
|
||||
def feeds():
|
||||
feeds = flask_login.current_user.feeds
|
||||
return flask.render_template('feeds.jinja2', feeds=feeds)
|
||||
sorted_feeds = sorted(feeds, key=lambda f: f.name and f.name.lower())
|
||||
return flask.render_template('feeds.jinja2', feeds=sorted_feeds)
|
||||
|
||||
|
||||
@views.route('/settings')
|
||||
@flask_login.login_required
|
||||
def settings():
|
||||
return flask.render_template('settings.jinja2')
|
||||
|
||||
|
||||
@views.route('/update_feed')
|
||||
|
@ -79,21 +87,19 @@ def edit_feed():
|
|||
def logout():
|
||||
flask_login.logout_user()
|
||||
return flask.redirect(flask.url_for('.index'))
|
||||
|
||||
|
||||
@views.route('/login')
|
||||
|
||||
@views.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
me = flask.request.args.get('me')
|
||||
if me:
|
||||
return micropub.authorize(
|
||||
me, flask.url_for('.login_callback', _external=True),
|
||||
next_url=flask.request.args.get('next'),
|
||||
scope='post')
|
||||
if flask.request.method == 'POST':
|
||||
return micropub.authenticate(
|
||||
flask.request.form.get('me'),
|
||||
next_url=flask.request.form.get('next'))
|
||||
return flask.render_template('login.jinja2')
|
||||
|
||||
|
||||
@views.route('/login-callback')
|
||||
@micropub.authorized_handler
|
||||
@micropub.authenticated_handler
|
||||
def login_callback(resp):
|
||||
if not resp.me:
|
||||
flask.flash(cgi.escape('Login error: ' + resp.error))
|
||||
|
@ -109,14 +115,50 @@ def login_callback(resp):
|
|||
user.domain = domain
|
||||
db.session.add(user)
|
||||
|
||||
user.url = resp.me
|
||||
db.session.commit()
|
||||
flask_login.login_user(user, remember=True)
|
||||
return flask.redirect(resp.next_url or flask.url_for('.index'))
|
||||
|
||||
|
||||
@views.route('/authorize', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def authorize():
|
||||
return micropub.authorize(
|
||||
me=flask_login.current_user.url,
|
||||
next_url=flask.request.form.get('next'),
|
||||
scope='post')
|
||||
|
||||
|
||||
@views.route('/micropub-callback')
|
||||
@micropub.authorized_handler
|
||||
def micropub_callback(resp):
|
||||
if not resp.me or resp.error:
|
||||
flask.flash(cgi.escape('Authorize error: ' + resp.error))
|
||||
return flask.redirect(flask.url_for('.login'))
|
||||
|
||||
domain = urllib.parse.urlparse(resp.me).netloc
|
||||
user = load_user(domain)
|
||||
if not user:
|
||||
flask.flash(cgi.escape('Unknown user for domain: ' + domain))
|
||||
return flask.redirect(flask.url_for('.login'))
|
||||
|
||||
user.micropub_endpoint = resp.micropub_endpoint
|
||||
user.access_token = resp.access_token
|
||||
db.session.commit()
|
||||
|
||||
flask_login.login_user(user, remember=True)
|
||||
return flask.redirect(resp.next_url or flask.url_for('.index'))
|
||||
|
||||
|
||||
@views.route('/deauthorize', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def deauthorize():
|
||||
flask_login.current_user.micropub_endpoint = None
|
||||
flask_login.current_user.access_token = None
|
||||
db.session.commit()
|
||||
return flask.redirect(flask.request.form.get('next')
|
||||
or flask.url_for('.index'))
|
||||
|
||||
|
||||
@login_mgr.user_loader
|
||||
def load_user(domain):
|
||||
return User.query.filter_by(domain=domain).first()
|
||||
|
@ -156,7 +198,8 @@ def add_subscription(origin, feed_url, type):
|
|||
if not feed:
|
||||
if type == 'html':
|
||||
flask.current_app.logger.debug('mf2py parsing %s', feed_url)
|
||||
parsed = mf2util.interpret_feed(mf2py.parse(url=feed_url), feed_url)
|
||||
parsed = mf2util.interpret_feed(
|
||||
mf2py.Parse(url=feed_url).to_dict(), feed_url)
|
||||
name = parsed.get('name')
|
||||
if not name or len(name) > 140:
|
||||
p = urllib.parse.urlparse(origin)
|
||||
|
@ -215,7 +258,8 @@ def find_possible_feeds(origin):
|
|||
'type': 'xml',
|
||||
})
|
||||
|
||||
hfeed = mf2util.interpret_feed(mf2py.parse(doc=resp.text), origin)
|
||||
hfeed = mf2util.interpret_feed(
|
||||
mf2py.Parser(doc=resp.text).to_dict(), origin)
|
||||
if hfeed.get('entries'):
|
||||
feeds.append({
|
||||
'origin': origin,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue