Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
ahopkins committed Aug 6, 2017
0 parents commit b1134cd
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 0 deletions.
105 changes: 105 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/


# AMH Additional
NOTES
Empty file added MANIFEST.in
Empty file.
64 changes: 64 additions & 0 deletions example/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from sanic import Sanic
from sanic.response import json
from aoiklivereload import LiveReloader
from sanic_jwt.decorators import protected
# from sanic_jwt.auth_bp import bp as sanic_jwt_auth_bp
from sanic_jwt import initialize, exceptions


reloader = LiveReloader()
reloader.start_watcher_thread()


def authenticate(request, *args, **kwargs):
username = request.json.get('username', None)
password = request.json.get('password', None)

if not username or not password:
raise exceptions.AuthenticationFailed("Missing username or password.")

user = username_table.get(username, None)
if user is None:
raise exceptions.AuthenticationFailed("User not found.")

if password != user.password:
raise exceptions.AuthenticationFailed("Password is incorrect.")

return user


app = Sanic()
initialize(app, authenticate)


class User(object):
def __init__(self, id, username, password):
setattr(self, app.config.SANIC_JWT_USER_ID, id)
self.username = username
self.password = password

def __str__(self):
return "User(id='%s')" % self.id


users = [
User(1, 'user1', 'abcxyz'),
User(2, 'user2', 'abcxyz'),
]

username_table = {u.username: u for u in users}
userid_table = {getattr(u, app.config.SANIC_JWT_USER_ID): u for u in users}


@app.route("/")
async def test(request):
return json({"hello": "world"})


@app.route("/protected")
@protected()
async def test(request):
return json({"protected": True})

if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000)
13 changes: 13 additions & 0 deletions example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
aiofiles==0.3.1
AoikLiveReload==0.1.0
argh==0.26.2
httptools==0.0.9
pathtools==0.1.2
PyJWT==1.5.2
PyYAML==3.12
sanic==0.6.0
sanic-jwt==0.1.0
ujson==1.35
uvloop==0.8.0
watchdog==0.8.3
websockets==3.3
9 changes: 9 additions & 0 deletions sanic_jwt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from sanic_jwt.blueprint import bp as sanic_jwt_auth_bp
from sanic_jwt.authentication import SanicJWTAuthentication
from sanic_jwt import settings


def initialize(app, authenticate):
app.blueprint(sanic_jwt_auth_bp, url_prefix='/auth')
app.auth = SanicJWTAuthentication(app, authenticate)
app.config.from_object(settings)
76 changes: 76 additions & 0 deletions sanic_jwt/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import importlib
import jwt

from sanic_jwt import exceptions


class BaseAuthentication(object):
def __init__(self, app, authenticate):
self.app = app
self.authenticate = authenticate


class SanicJWTAuthentication(BaseAuthentication):
def _decode(self, token):
secret = self._get_secret()
algorithm = self._get_algorithm()
return jwt.decode(token, secret, algorithms=[algorithm])

def _get_algorithm(self):
return self.app.config.SANIC_JWT_ALGORITHM

def _get_payload(self, user):
parts = self.app.config.SANIC_JWT_PAYLOAD_HANDLER.split('.')
fn = parts.pop()
module = importlib.import_module('.'.join(parts))
method = getattr(module, fn)
payload = method(self, user)

return payload

def _get_secret(self):
return self.app.config.SANIC_JWT_SECRET

def _get_token(self, request):
header = request.headers.get(self.app.config.SANIC_JWT_AUTHORIZATION_HEADER, None)
if header:
try:
prefix, token = header.split(' ')
if prefix != self.app.config.SANIC_JWT_AUTHORIZATION_HEADER_PREFIX:
raise Exception
except Exception:
raise exceptions.InvalidAuthorizationHeader()

return token

raise exceptions.MissingAuthorizationHeader()

def get_access_token(self, user):
payload = self._get_payload(user)
secret = self._get_secret()
algorithm = self._get_algorithm()

return jwt.encode(payload, secret, algorithm=algorithm)

def is_authenticated(self, request, *args, **kwargs):
try:
is_valid, _, __ = self.verify(request, *args, **kwargs)
except Exception:
raise exceptions.Unauthorized()

return is_valid

def verify(self, request, *args, **kwargs):
token = self._get_token(request)
is_valid = True
reason = None

try:
self._decode(token)
except jwt.exceptions.ExpiredSignatureError:
is_valid = False
reason = 'Signature has expired'

status = 200 if is_valid else 400

return is_valid, status, reason
31 changes: 31 additions & 0 deletions sanic_jwt/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from sanic.response import json
from sanic import Blueprint


bp = Blueprint('auth_bp')


@bp.post('/')
async def authenticate(request, *args, **kwargs):
try:
user = request.app.auth.authenticate(request, *args, **kwargs)
except Exception as e:
raise e


return json({
'access_token': request.app.auth.get_access_token(user)
})

@bp.get('/verify')
async def verify(request, *args, **kwargs):
is_valid, status, reason = request.app.auth.verify(request, *args, **kwargs)

response = {
'valid': is_valid
}

if reason:
response.update({'reason': reason})

return json(response, status=status)
22 changes: 22 additions & 0 deletions sanic_jwt/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from functools import wraps
from sanic.response import json


def protected():
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
is_authorized = request.app.auth.is_authenticated(request, *args, **kwargs)

if is_authorized:
# the user is authorized.
# run the handler method and return the response
response = await f(request, *args, **kwargs)
return response
else:
# the user is not authorized.
return json({
'status': 'not_authorized',
}, 403)
return decorated_function
return decorator
24 changes: 24 additions & 0 deletions sanic_jwt/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from sanic.exceptions import SanicException, Unauthorized as SanicUnauthorized, add_status_code


@add_status_code(401)
class AuthenticationFailed(SanicException):
def __init__(self, message="Authentication failed."):
super().__init__(message)


@add_status_code(400)
class MissingAuthorizationHeader(SanicException):
def __init__(self, message="Authorization header not present."):
super().__init__(message)


@add_status_code(400)
class InvalidAuthorizationHeader(SanicException):
def __init__(self, message="Authorization header is invalid."):
super().__init__(message)


class Unauthorized(SanicUnauthorized):
def __init__(self):
super().__init__("Auth required.", "Bearer")
12 changes: 12 additions & 0 deletions sanic_jwt/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from datetime import datetime, timedelta


def build_payload(authenticator, user):
user_id = getattr(user, authenticator.app.config.SANIC_JWT_USER_ID)
delta = timedelta(seconds=authenticator.app.config.SANIC_JWT_EXPIRATION_DELTA)
exp = datetime.utcnow() + delta

return {
'user_id': user_id,
'exp': exp,
}
7 changes: 7 additions & 0 deletions sanic_jwt/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SANIC_JWT_ALGORITHM = 'HS256'
SANIC_JWT_AUTHORIZATION_HEADER = 'authorization'
SANIC_JWT_AUTHORIZATION_HEADER_PREFIX = 'Bearer'
SANIC_JWT_EXPIRATION_DELTA = 60 * 5 * 6
SANIC_JWT_PAYLOAD_HANDLER = 'sanic_jwt.handlers.build_payload'
SANIC_JWT_SECRET = 'This is a big secret. Shhhhh'
SANIC_JWT_USER_ID = 'user_id'
Loading

0 comments on commit b1134cd

Please sign in to comment.