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
|
-e .
|
||||||
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
|
|
||||||
|
|
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.Table(
|
||||||
'users_to_feeds', db.Model.metadata,
|
'users_to_feeds', db.Model.metadata,
|
||||||
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), index=True),
|
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), index=True),
|
||||||
|
@ -21,6 +20,7 @@ users_to_feeds = db.Table(
|
||||||
|
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
url = db.Column(db.String(256))
|
||||||
domain = db.Column(db.String(256))
|
domain = db.Column(db.String(256))
|
||||||
micropub_endpoint = db.Column(db.String(512))
|
micropub_endpoint = db.Column(db.String(512))
|
||||||
access_token = db.Column(db.String(512))
|
access_token = db.Column(db.String(512))
|
||||||
|
|
|
@ -369,6 +369,15 @@ header, main {
|
||||||
header {
|
header {
|
||||||
margin-bottom: 1em; }
|
margin-bottom: 1em; }
|
||||||
|
|
||||||
|
ul#navigation {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
float: right; }
|
||||||
|
ul#navigation li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px; }
|
||||||
|
|
||||||
.pager {
|
.pager {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 1em 0; }
|
margin: 1em 0; }
|
||||||
|
@ -404,8 +413,19 @@ article {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-weight: bold; }
|
font-weight: bold; }
|
||||||
|
|
||||||
form textarea {
|
label {
|
||||||
width: 90%; }
|
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) {
|
@media only screen and (max-width: 800px) {
|
||||||
article header img {
|
article header img {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"mappings": "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",
|
"mappings": "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",
|
||||||
"sources": ["normalize.scss","style.scss"],
|
"sources": ["normalize.scss","style.scss"],
|
||||||
"names": [],
|
"names": [],
|
||||||
"file": "style.css"
|
"file": "style.css"
|
||||||
|
|
|
@ -30,6 +30,18 @@ header {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul#navigation {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pager {
|
.pager {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
@ -44,7 +56,6 @@ header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
article {
|
article {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
box-shadow: $box-shadow;
|
box-shadow: $box-shadow;
|
||||||
|
@ -79,10 +90,22 @@ article {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
label {
|
||||||
textarea {
|
font-weight: bold;
|
||||||
width: 90%;
|
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) {
|
@media only screen and (max-width: 800px) {
|
||||||
|
|
|
@ -5,23 +5,38 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Woodwind</title>
|
<title>Woodwind</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"/>
|
<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 %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1 class="h-x-app">Woodwind</h1>
|
|
||||||
|
|
||||||
{% if current_user.is_authenticated() %}
|
{% if current_user.is_authenticated() %}
|
||||||
<div class="logged-in">{{ current_user.domain }}
|
<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>)
|
(<a href="{{ url_for('.logout') }}">Logout</a>)
|
||||||
</div>
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h1 class="h-x-app">Woodwind</h1>
|
||||||
{% else %}
|
{% 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="text" name="me" placeholder="mydomain.com" />
|
||||||
|
<input type="hidden" name="next" placeholder="{{ request.path }}" />
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block header %}{% endblock %}
|
{% block header %}{% endblock %}
|
||||||
{% for message in get_flashed_messages() %}
|
{% for message in get_flashed_messages() %}
|
||||||
<div class="flash">{{ message | safe }}</div>
|
<div class="flash">{{ message | safe }}</div>
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
{% extends "base.jinja2" %}
|
{% extends "base.jinja2" %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<script src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
|
|
||||||
<script src="{{url_for('static', filename='feed.js')}}"></script>
|
<script src="{{url_for('static', filename='feed.js')}}"></script>
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% if current_user.is_authenticated() %}
|
{% if current_user.is_authenticated() %}
|
||||||
<form action="{{ url_for('.subscribe') }}" method="POST">
|
<form action="{{ url_for('.subscribe') }}" method="POST">
|
||||||
<input type="url" id="origin" name="origin" placeholder="Feed URL" />
|
<input type="url" id="origin" name="origin" placeholder="Subscribe to URL" />
|
||||||
<button type="submit" id="subscribe">Subscribe</button>
|
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock header %}
|
{% endblock header %}
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
{% extends "base.jinja2" %}
|
{% 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 %}
|
{% block body %}
|
||||||
|
|
||||||
{% for feed in feeds %}
|
{% for feed in feeds %}
|
||||||
|
<article>
|
||||||
<div>
|
|
||||||
<form style="display:inline"
|
<form style="display:inline"
|
||||||
action="{{ url_for('.edit_feed') }}" method="POST">
|
action="{{ url_for('.edit_feed') }}" method="POST">
|
||||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||||
<input type="text" name="name" style="width:25%" value="{{ feed.name }}"/>
|
<label>Name</label>
|
||||||
<input type="text" name="feed" style="width:25%" value="{{ feed.feed }}"/>
|
<input type="text" name="name" value="{{ feed.name }}"/>
|
||||||
|
<label>URL</label>
|
||||||
|
<input type="text" name="feed" value="{{ feed.feed }}"/>
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
<form style="display:inline;"
|
<form style="display:inline;"
|
||||||
|
@ -21,8 +31,7 @@
|
||||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||||
<button type="submit">Delete</button>
|
<button type="submit">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</article>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock body %}
|
{% 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')
|
@views.route('/feeds')
|
||||||
|
@flask_login.login_required
|
||||||
def feeds():
|
def feeds():
|
||||||
feeds = flask_login.current_user.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')
|
@views.route('/update_feed')
|
||||||
|
@ -81,19 +89,17 @@ def logout():
|
||||||
return flask.redirect(flask.url_for('.index'))
|
return flask.redirect(flask.url_for('.index'))
|
||||||
|
|
||||||
|
|
||||||
@views.route('/login')
|
@views.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
me = flask.request.args.get('me')
|
if flask.request.method == 'POST':
|
||||||
if me:
|
return micropub.authenticate(
|
||||||
return micropub.authorize(
|
flask.request.form.get('me'),
|
||||||
me, flask.url_for('.login_callback', _external=True),
|
next_url=flask.request.form.get('next'))
|
||||||
next_url=flask.request.args.get('next'),
|
|
||||||
scope='post')
|
|
||||||
return flask.render_template('login.jinja2')
|
return flask.render_template('login.jinja2')
|
||||||
|
|
||||||
|
|
||||||
@views.route('/login-callback')
|
@views.route('/login-callback')
|
||||||
@micropub.authorized_handler
|
@micropub.authenticated_handler
|
||||||
def login_callback(resp):
|
def login_callback(resp):
|
||||||
if not resp.me:
|
if not resp.me:
|
||||||
flask.flash(cgi.escape('Login error: ' + resp.error))
|
flask.flash(cgi.escape('Login error: ' + resp.error))
|
||||||
|
@ -109,14 +115,50 @@ def login_callback(resp):
|
||||||
user.domain = domain
|
user.domain = domain
|
||||||
db.session.add(user)
|
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.micropub_endpoint = resp.micropub_endpoint
|
||||||
user.access_token = resp.access_token
|
user.access_token = resp.access_token
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
flask_login.login_user(user, remember=True)
|
|
||||||
return flask.redirect(resp.next_url or flask.url_for('.index'))
|
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
|
@login_mgr.user_loader
|
||||||
def load_user(domain):
|
def load_user(domain):
|
||||||
return User.query.filter_by(domain=domain).first()
|
return User.query.filter_by(domain=domain).first()
|
||||||
|
@ -156,7 +198,8 @@ def add_subscription(origin, feed_url, type):
|
||||||
if not feed:
|
if not feed:
|
||||||
if type == 'html':
|
if type == 'html':
|
||||||
flask.current_app.logger.debug('mf2py parsing %s', feed_url)
|
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')
|
name = parsed.get('name')
|
||||||
if not name or len(name) > 140:
|
if not name or len(name) > 140:
|
||||||
p = urllib.parse.urlparse(origin)
|
p = urllib.parse.urlparse(origin)
|
||||||
|
@ -215,7 +258,8 @@ def find_possible_feeds(origin):
|
||||||
'type': 'xml',
|
'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'):
|
if hfeed.get('entries'):
|
||||||
feeds.append({
|
feeds.append({
|
||||||
'origin': origin,
|
'origin': origin,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue