Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ General Options:
in a sequence or a set to check more then one location, such as:
``('headers', 'cookies')``. Defaults to ``['headers']``
``JWT_ACCESS_TOKEN_EXPIRES`` How long an access token should live before it expires. This
takes a ``datetime.timedelta`` or an ``int`` (seconds), and defaults to 15 minutes.
takes any value that can be safely added to a ``datetime.datetime`` object, including
``datetime.timedelta``, `dateutil.relativedelta <https://dateutil.readthedocs.io/en/stable/relativedelta.html>`_,
or an ``int`` (seconds), and defaults to 15 minutes.
Can be set to ``False`` to disable expiration.
``JWT_REFRESH_TOKEN_EXPIRES`` How long a refresh token should live before it expires. This
takes a ``datetime.timedelta`` or ``int`` (seconds), and defaults to 30 days.
takes any value that can be safely added to a ``datetime.datetime`` object, including
``datetime.timedelta``, `dateutil.relativedelta <https://dateutil.readthedocs.io/en/stable/relativedelta.html>`_,
or an ``int`` (seconds), and defaults to 30 days.
Can be set to ``False`` to disable expiration.
``JWT_ALGORITHM`` Which algorithm to sign the JWT with. `See here <https://pyjwt.readthedocs.io/en/latest/algorithms.html>`_
for the options. Defaults to ``'HS256'``.
Expand Down
25 changes: 17 additions & 8 deletions flask_jwt_extended/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from warnings import warn
from six import raise_from

# In Python 2.7 collections.abc is a part of the collections module.
try:
Expand Down Expand Up @@ -188,21 +189,29 @@ def access_expires(self):
delta = current_app.config['JWT_ACCESS_TOKEN_EXPIRES']
if type(delta) is int:
delta = datetime.timedelta(seconds=delta)
if not isinstance(delta, datetime.timedelta) and delta is not False:
err = 'JWT_ACCESS_TOKEN_EXPIRES must be a ' \
'datetime.timedelta, int or False'
raise RuntimeError(err)
if delta is not False:
try:
delta + datetime.datetime.now()
except TypeError as e:
err = (
"must be able to add JWT_ACCESS_TOKEN_EXPIRES to datetime.datetime"
)
raise_from(RuntimeError(err), e)
return delta

@property
def refresh_expires(self):
delta = current_app.config['JWT_REFRESH_TOKEN_EXPIRES']
if type(delta) is int:
delta = datetime.timedelta(seconds=delta)
if not isinstance(delta, datetime.timedelta) and delta is not False:
err = 'JWT_REFRESH_TOKEN_EXPIRES must be a ' \
'datetime.timedelta, int or False'
raise RuntimeError(err)
if delta is not False:
try:
delta + datetime.datetime.now()
except TypeError as e:
err = (
"must be able to add JWT_REFRESH_TOKEN_EXPIRES to datetime.datetime"
)
raise_from(RuntimeError(err), e)
return delta

@property
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
'Werkzeug>=0.14', # Needed for SameSite cookie functionality
'Flask',
'PyJWT',
'six',
],
extras_require={
'asymmetric_crypto': ["cryptography >= 2.3"]
Expand Down
12 changes: 7 additions & 5 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from flask import Flask
from flask.json import JSONEncoder

Expand Down Expand Up @@ -72,7 +73,8 @@ def test_default_configs(app):
assert config.error_msg_key == 'msg'


def test_override_configs(app):
@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
def test_override_configs(app, delta_func):
app.config['JWT_TOKEN_LOCATION'] = ['cookies', 'query_string', 'json']
app.config['JWT_HEADER_NAME'] = 'TestHeader'
app.config['JWT_HEADER_TYPE'] = 'TestType'
Expand Down Expand Up @@ -100,8 +102,8 @@ def test_override_configs(app):
app.config['JWT_ACCESS_CSRF_HEADER_NAME'] = 'X-ACCESS-CSRF'
app.config['JWT_REFRESH_CSRF_HEADER_NAME'] = 'X-REFRESH-CSRF'

app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=5)
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=5)
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = delta_func(minutes=5)
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = delta_func(days=5)
app.config['JWT_ALGORITHM'] = 'HS512'

app.config['JWT_BLACKLIST_ENABLED'] = True
Expand Down Expand Up @@ -151,8 +153,8 @@ class CustomJSONEncoder(JSONEncoder):
assert config.access_csrf_header_name == 'X-ACCESS-CSRF'
assert config.refresh_csrf_header_name == 'X-REFRESH-CSRF'

assert config.access_expires == timedelta(minutes=5)
assert config.refresh_expires == timedelta(days=5)
assert config.access_expires == delta_func(minutes=5)
assert config.refresh_expires == delta_func(days=5)
assert config.algorithm == 'HS512'

assert config.blacklist_enabled is True
Expand Down
11 changes: 7 additions & 4 deletions tests/test_decode_tokens.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jwt
import pytest
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import warnings

from flask import Flask
Expand Down Expand Up @@ -103,9 +104,10 @@ def test_bad_token_type(app, default_access_token):
decode_token(bad_type_token)


def test_expired_token(app):
@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
def test_expired_token(app, delta_func):
with app.test_request_context():
delta = timedelta(minutes=-5)
delta = delta_func(minutes=-5)
access_token = create_access_token('username', expires_delta=delta)
refresh_token = create_refresh_token('username', expires_delta=delta)
with pytest.raises(ExpiredSignatureError):
Expand All @@ -114,9 +116,10 @@ def test_expired_token(app):
decode_token(refresh_token)


def test_allow_expired_token(app):
@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
def test_allow_expired_token(app, delta_func):
with app.test_request_context():
delta = timedelta(minutes=-5)
delta = delta_func(minutes=-5)
access_token = create_access_token('username', expires_delta=delta)
refresh_token = create_refresh_token('username', expires_delta=delta)
for token in (access_token, refresh_token):
Expand Down
11 changes: 7 additions & 4 deletions tests/test_view_decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import warnings
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from flask import Flask, jsonify

from flask_jwt_extended import (
Expand Down Expand Up @@ -146,7 +147,8 @@ def test_refresh_jwt_required(app):
assert response.get_json() == {'foo': 'bar'}


def test_jwt_optional(app):
@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
def test_jwt_optional(app, delta_func):
url = '/optional_protected'

test_client = app.test_client()
Expand All @@ -156,7 +158,7 @@ def test_jwt_optional(app):
refresh_token = create_refresh_token('username')
expired_token = create_access_token(
identity='username',
expires_delta=timedelta(minutes=-1)
expires_delta=delta_func(minutes=-1)
)

response = test_client.get(url, headers=make_headers(fresh_access_token))
Expand Down Expand Up @@ -235,12 +237,13 @@ def test_jwt_invalid_audience(app):
assert response.get_json() == {'msg': 'Invalid audience'}


def test_expired_token(app):
@pytest.mark.parametrize("delta_func", [timedelta, relativedelta])
def test_expired_token(app, delta_func):
url = '/protected'
jwtM = get_jwt_manager(app)
test_client = app.test_client()
with app.test_request_context():
token = create_access_token('username', expires_delta=timedelta(minutes=-1))
token = create_access_token('username', expires_delta=delta_func(minutes=-1))

# Test default response
response = test_client.get(url, headers=make_headers(token))
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ deps =
pytest
coverage
cryptography
python-dateutil
# TODO why does this not work?
# extras =
# asymmetric_crypto
Expand Down