Switch from database-persisted auth codes to stateless JSON Web Tokens :)

This commit is contained in:
Danielle McLean 2017-11-02 16:36:16 +11:00
parent 41d490ea80
commit 1c09be1b1c
Signed by untrusted user: 00dani
GPG key ID: 5A5D2D1AFF12EEC5
6 changed files with 72 additions and 56 deletions

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-11-02 05:35
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('lemonauth', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='IndieAuthCode',
),
]

View file

@ -1,29 +0,0 @@
from django.db import models
from secrets import token_hex
class IndieAuthCodeManager(models.Manager):
def create_from_qdict(self, d):
code = self.create(
me=d['me'],
client_id=d['client_id'],
redirect_uri=d['redirect_uri'],
response_type=d.get('response_type', 'id'),
scope=" ".join(d.getlist('scope')),
)
code.code = token_hex(32)
return code
class IndieAuthCode(models.Model):
objects = IndieAuthCodeManager()
code = models.CharField(max_length=64, unique=True)
me = models.CharField(max_length=255)
client_id = models.CharField(max_length=255)
redirect_uri = models.CharField(max_length=255)
response_type = models.CharField(
max_length=4,
choices=(('id', 'id'), ('code', 'code')),
default='id',
)
scope = models.CharField(max_length=200, blank=True)

27
lemonauth/tokens.py Normal file
View file

@ -0,0 +1,27 @@
import jwt
from datetime import datetime, timedelta
from django.conf import settings
def gen_auth_code(post):
params = {'me': post['me']}
if 'state' in post:
params['state'] = post['state']
code = {
'me': post['me'],
'client_id': post['client_id'],
'redirect_uri': post['redirect_uri'],
'response_type': post.get('response_type', 'id'),
'exp': datetime.utcnow() + timedelta(minutes=10),
}
if 'scope' in post:
code['scope'] = ' '.join(post.getlist('scope'))
params['code'] = jwt.encode(code, settings.SECRET_KEY)
return params
def verify_auth_code(c):
return jwt.decode(c, settings.SECRET_KEY)

View file

@ -11,7 +11,7 @@ from django.views.decorators.http import require_POST
from lemoncurry import breadcrumbs, utils
from urllib.parse import urlencode, urljoin, urlunparse, urlparse
from ..models import IndieAuthCode
from .. import tokens
breadcrumbs.add('lemonauth:indie', label='indieauth', parent='home:index')
@ -88,28 +88,23 @@ class IndieView(TemplateView):
def post(self, request):
post = request.POST.dict()
try:
code = IndieAuthCode.objects.get(code=post.get('code'))
except IndieAuthCode.DoesNotExist:
code = tokens.verify_auth_code(post.get('code'))
except Exception:
# if anything at all goes wrong when decoding the auth code, bail
# out immediately.
return utils.forbid('invalid auth code')
# We always delete the code immediately to ensure it's only single-use.
# If you pass the right code but the wrong other info, bad luck, you
# need a new code.
code.delete()
# After deleting the code from the DB, we verify the other parameters
# of the request.
if code.response_type != 'id':
if code['response_type'] != 'id':
return utils.bad_req(
'this endpoint only supports response_type=id'
)
if post.get('client_id') != code.client_id:
if post.get('client_id') != code['client_id']:
return utils.forbid('client id did not match')
if post.get('redirect_uri') != code.redirect_uri:
if post.get('redirect_uri') != code['redirect_uri']:
return utils.forbid('redirect uri did not match')
# If we got here, it's valid! Yay!
return utils.choose_type(request, {'me': code.me}, {
return utils.choose_type(request, {'me': code['me']}, {
'application/json': JsonResponse,
'application/x-www-form-urlencoded': utils.form_encoded_response,
})
@ -118,9 +113,6 @@ class IndieView(TemplateView):
@login_required
@require_POST
def approve(request):
code = IndieAuthCode.objects.create_from_qdict(request.POST)
code.save()
params = {'code': code.code, 'me': code.me}
if 'state' in request.POST:
params['state'] = request.POST['state']
return redirect(code.redirect_uri + '?' + urlencode(params))
post = request.POST
params = tokens.gen_auth_code(post)
return redirect(post['redirect_uri'] + '?' + urlencode(params))