Skip to content

Commit 5e3167e

Browse files
authored
Merge branch 'master' into add-django-axes
2 parents 6568666 + f38a27c commit 5e3167e

File tree

10 files changed

+172
-31
lines changed

10 files changed

+172
-31
lines changed

.github/workflows/docker-base.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ jobs:
1717
uses: actions/checkout@v3
1818

1919
- name: Set up QEMU
20-
uses: docker/setup-qemu-action@v2.0.0
20+
uses: docker/setup-qemu-action@v2.1.0
2121

2222
- name: Set up Docker Buildx
23-
uses: docker/setup-buildx-action@v2.0.0
23+
uses: docker/setup-buildx-action@v2.1.0
2424

2525
- name: Login to DockerHub
2626
uses: docker/login-action@v2.0.0
@@ -29,7 +29,7 @@ jobs:
2929
password: ${{ secrets.DOCKERHUB_TOKEN }}
3030

3131
- name: Build base image
32-
uses: docker/build-push-action@v3.1.1
32+
uses: docker/build-push-action@v3.2.0
3333
with:
3434
context: .
3535
push: true

.github/workflows/docker.yml

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,58 @@ on:
66
- master
77

88
jobs:
9-
path-context:
9+
apache:
10+
name: Build apache image
1011
runs-on: ubuntu-latest
1112
steps:
1213
- name: Checkout
1314
uses: actions/checkout@v3
1415

1516
- name: Set up QEMU
16-
uses: docker/setup-qemu-action@v2.0.0
17+
uses: docker/setup-qemu-action@v2.1.0
1718

1819
- name: Set up Docker Buildx
19-
uses: docker/setup-buildx-action@v2.0.0
20+
uses: docker/setup-buildx-action@v2.1.0
2021

2122
- name: Login to DockerHub
2223
uses: docker/login-action@v2.0.0
2324
with:
2425
username: ${{ secrets.DOCKERHUB_USERNAME }}
2526
password: ${{ secrets.DOCKERHUB_TOKEN }}
2627

27-
- name: Build apache image
28-
uses: docker/build-push-action@v3.1.1
28+
- name: Build image
29+
uses: docker/build-push-action@v3.2.0
2930
with:
3031
context: .
3132
push: true
3233
file: extras/docker/demo/Dockerfile
3334
platforms: linux/amd64,linux/arm64
34-
tags: wger/demo:latest,wger/demo:2.1-dev,wger/apache:latest,wger/apache:2.1-dev
35+
tags: wger/demo:latest,wger/demo:2.2-dev,wger/apache:latest,wger/apache:2.2-dev
3536

36-
- name: Build dev image
37-
uses: docker/build-push-action@v3.1.1
37+
prod:
38+
name: Build production image
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v3
43+
44+
- name: Set up QEMU
45+
uses: docker/setup-qemu-action@v2.1.0
46+
47+
- name: Set up Docker Buildx
48+
uses: docker/setup-buildx-action@v2.1.0
49+
50+
- name: Login to DockerHub
51+
uses: docker/login-action@v2.0.0
52+
with:
53+
username: ${{ secrets.DOCKERHUB_USERNAME }}
54+
password: ${{ secrets.DOCKERHUB_TOKEN }}
55+
56+
- name: Build image
57+
uses: docker/build-push-action@v3.2.0
3858
with:
3959
context: .
4060
push: true
4161
file: extras/docker/development/Dockerfile
4262
platforms: linux/amd64,linux/arm64
43-
tags: wger/server:latest,wger/server:2.1-dev,wger/devel:latest,wger/devel:2.1-dev
63+
tags: wger/server:latest,wger/server:2.2-dev,wger/devel:latest,wger/devel:2.2-dev

extras/docker/development/settings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
TIME_ZONE = env.str("TIME_ZONE", 'Europe/Berlin')
4545

4646
# Make this unique, and don't share it with anybody.
47-
SECRET_KEY = env.str("SECRET_KEY", 'wger-django-secret-key')
47+
SECRET_KEY = env.str("SECRET_KEY", 'wger-docker-supersecret-key-1234567890!@#$%^&*(-_)')
4848

4949

5050
# Your reCaptcha keys
@@ -122,3 +122,10 @@
122122
AXES_FAILURE_LIMIT = 5 # configurable, default is 5
123123
AXES_COOLOFF_TIME = 0.5 # configurable, default is 0.5 hours
124124
AXES_HANDLER = 'axes.handlers.cache.AxesCacheHandler' # Configurable, but default is the cache handler
125+
126+
#
127+
# Django Rest Framework SimpleJWT
128+
#
129+
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(minutes=env.int("ACCESS_TOKEN_LIFETIME", 15))
130+
SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] = timedelta(hours=env.int("REFRESH_TOKEN_LIFETIME", 24))
131+
SIMPLE_JWT['SIGNING_KEY'] = env.str("SIGNING_KEY", SECRET_KEY)

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ django-activity-stream~=1.4
1212
django-axes==5.39.0
1313
django-crispy-forms~=1.14
1414
django-simple-history~=3.1
15-
django-email-verification~=0.1.0
15+
django-email-verification~=0.3.1
1616
django_compressor~=4.1
1717
django_extensions~=3.2
1818
django-storages~=1.13
1919
django-environ==0.9.0
2020
easy-thumbnails==2.8.3
21-
fontawesomefree~=6.1.1
21+
fontawesomefree~=6.2.0
2222
icalendar==4.1.0
2323
invoke==1.7.3
2424
pillow==9.2.0
@@ -33,6 +33,7 @@ requests==2.28.1
3333
django-cors-headers==3.13.0
3434
django-filter==22.1
3535
djangorestframework~=3.14
36+
djangorestframework-simplejwt[crypto]==5.2.1
3637

3738
# Not used anymore, but needed because some modules are imported in DB migration
3839
# files

wger/core/api/views.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,23 @@ def get(request):
198198
class UserAPILoginView(viewsets.ViewSet):
199199
"""
200200
API endpoint for api user objects
201+
.. warning:: This endpoint is deprecated
201202
"""
202203
permission_classes = (AllowAny, )
203204
queryset = User.objects.all()
204205
serializer_class = UserApiSerializer
205206
throttle_scope = 'login'
206207

207208
def get(self, request):
208-
return Response({'message': "You must send a 'username' and 'password' via POST"})
209+
return Response(
210+
data={
211+
'message': "You must send a 'username' and 'password' via POST",
212+
'warning': "This endpoint is deprecated."
213+
},
214+
headers={
215+
"Deprecation": "Sat, 01 Oct 2022 23:59:59 GMT",
216+
},
217+
)
209218

210219
def post(self, request):
211220
data = request.data
@@ -223,7 +232,16 @@ def post(self, request):
223232
)
224233

225234
token = create_token(form.get_user())
226-
return Response({'token': token.key}, status=status.HTTP_200_OK)
235+
return Response(
236+
data={
237+
'token': token.key,
238+
'message': "This endpoint is deprecated."
239+
},
240+
status=status.HTTP_200_OK,
241+
headers={
242+
"Deprecation": "Sat, 01 Oct 2022 23:59:59 GMT",
243+
}
244+
)
227245

228246

229247
class UserAPIRegistrationViewSet(viewsets.ViewSet):

wger/exercises/models/exercise.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,12 @@ def get_absolute_url(self):
116116
"""
117117
Returns the canonical URL to view an exercise
118118
"""
119-
return reverse(
120-
'exercise:exercise:view-base',
121-
kwargs={
122-
'pk': self.exercise_base_id,
123-
'slug': slugify(self.name)
124-
}
125-
)
119+
slug_name = slugify(self.name)
120+
kwargs = {'pk': self.exercise_base_id}
121+
if slug_name:
122+
kwargs['slug'] = slug_name
123+
124+
return reverse('exercise:exercise:view-base', kwargs=kwargs)
126125

127126
def save(self, *args, **kwargs):
128127
"""
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# This file is part of wger Workout Manager.
2+
#
3+
# wger Workout Manager is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU Affero General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# wger Workout Manager is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Affero General Public License
14+
15+
# wger
16+
from wger.core.tests.base_testcase import WgerTestCase
17+
from wger.exercises.models import Exercise
18+
19+
20+
class ExerciseModelTestCase(WgerTestCase):
21+
"""
22+
Test the logic in the exercise model
23+
"""
24+
25+
def test_absolute_url_name(self):
26+
"""Test that the get_absolute_url returns the correct URL"""
27+
exercise = Exercise(exercise_base_id=1, description='abc', name='foo')
28+
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/1/view-base/foo')
29+
30+
def test_absolute_url_no_name(self):
31+
"""Test that the get_absolute_url returns the correct URL"""
32+
exercise = Exercise(exercise_base_id=2, description='abc', name='')
33+
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/2/view-base')
34+
35+
def test_absolute_url_no_name2(self):
36+
"""Test that the get_absolute_url returns the correct URL"""
37+
exercise = Exercise(exercise_base_id=42, description='abc', name='@@@@@')
38+
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/42/view-base')

wger/settings_global.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
1919
import os
2020
import re
21+
from datetime import timedelta
2122

2223

2324
"""
@@ -82,6 +83,7 @@
8283
'rest_framework',
8384
'rest_framework.authtoken',
8485
'django_filters',
86+
'rest_framework_simplejwt',
8587

8688
# Breadcrumbs
8789
'django_bootstrap_breadcrumbs',
@@ -418,6 +420,7 @@
418420
'DEFAULT_AUTHENTICATION_CLASSES': (
419421
'rest_framework.authentication.SessionAuthentication',
420422
'rest_framework.authentication.TokenAuthentication',
423+
'rest_framework_simplejwt.authentication.JWTAuthentication',
421424
),
422425
'DEFAULT_FILTER_BACKENDS': (
423426
'django_filters.rest_framework.DjangoFilterBackend',
@@ -429,6 +432,17 @@
429432
}
430433
}
431434

435+
#
436+
# Django Rest Framework SimpleJWT
437+
#
438+
SIMPLE_JWT = {
439+
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
440+
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
441+
'ROTATE_REFRESH_TOKENS': False,
442+
'BLACKLIST_AFTER_ROTATION': False,
443+
'UPDATE_LAST_LOGIN': False,
444+
}
445+
432446
#
433447
# CORS headers: allow all hosts to access the API
434448
#
@@ -489,8 +503,8 @@ def email_verified_callback(user):
489503
EMAIL_MAIL_SUBJECT = 'Confirm your email'
490504
EMAIL_MAIL_HTML = 'email_verification/email_body_html.tpl'
491505
EMAIL_MAIL_PLAIN = 'email_verification/email_body_txt.tpl'
492-
EMAIL_TOKEN_LIFE = 60 * 60
493-
EMAIL_PAGE_TEMPLATE = 'email_verification/confirm_template.html'
506+
EMAIL_MAIL_TOKEN_LIFE = 60 * 60
507+
EMAIL_MAIL_PAGE_TEMPLATE = 'email_verification/confirm_template.html'
494508
EMAIL_PAGE_DOMAIN = 'http://localhost:8000/'
495509

496510
#

wger/software/templates/api.html

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,47 @@ <h3>Authentication</h3>
1616
objects such as workouts, you need to generate an API KEY</strong> and pass
1717
it in the header, see the link on the sidebar for details.</p>
1818

19-
<p>You can also generate a token via the <code>login</code> endpoint. Send a
20-
username and password and you will get the user's token or a new one will be
21-
generated. At the moment it is not possible to register via the API.</p>
19+
<h6>JWT Authentication</h6>
20+
21+
<p>
22+
You can generate access token via <code>/token/</code> endpoint. Send a username and password, and you will get the
23+
<code>access</code> token which you can use to access the private endpoints.
24+
<pre>
25+
curl \
26+
-X POST \
27+
-H "Content-Type: application/json" \
28+
-d '{"username": "example_username", "password": "example_password "}' \
29+
https://wger.de/api/v2/token/
30+
31+
...
32+
{
33+
"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU",
34+
"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"
35+
}
36+
</pre>
37+
38+
<p>Additionally, you can send an access token to <code>/token/verify/</code> endpoint to verify that token.</p>
39+
40+
<p>When this short-lived access token expires, you can use the longer-lived <code>refresh</code>
41+
token to obtain another access token.
42+
<pre>
43+
curl \
44+
-X POST \
45+
-H "Content-Type: application/json" \
46+
-d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \
47+
https://wger.de/api/v2/token/refresh/
48+
49+
...
50+
{"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNTY3LCJqdGkiOiJjNzE4ZTVkNjgzZWQ0NTQyYTU0NWJkM2VmMGI0ZGQ0ZSJ9.ekxRxgb9OKmHkfy-zs1Ro_xs1eMLXiR17dIDBVxeT-w"}
51+
</pre>
52+
</p>
53+
</p>
2254

2355
<p>You should always use HTTPS if possible when communicating with the server.</p>
56+
<p>At the moment it is not possible to register via the API.</p>
57+
<p><strong>Deprecated: </strong>You can also generate a token via the <code>login</code> endpoint. Send a
58+
username and password, and you will get the user's token or a new one will be
59+
generated.</p>
2460

2561

2662
<div class="container">

wger/urls.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
# Third Party
3131
from django_email_verification import urls as email_urls
3232
from rest_framework import routers
33+
from rest_framework_simplejwt.views import (
34+
TokenObtainPairView,
35+
TokenRefreshView,
36+
TokenVerifyView,
37+
)
3338

3439
# wger
3540
from wger.core.api import views as core_api_views
@@ -44,7 +49,7 @@
4449
from wger.weight.api import views as weight_api_views
4550

4651

47-
#admin.autodiscover()
52+
# admin.autodiscover()
4853

4954
#
5055
# REST API
@@ -204,7 +209,7 @@
204209
# The actual URLs
205210
#
206211
urlpatterns = i18n_patterns(
207-
#url(r'^admin/', admin.site.urls),
212+
# url(r'^admin/', admin.site.urls),
208213
path('', include(('wger.core.urls', 'core'), namespace='core')),
209214
path('workout/', include(('wger.manager.urls', 'manager'), namespace='manager')),
210215
path('exercise/', include(('wger.exercises.urls', 'exercise'), namespace='exercise')),
@@ -244,6 +249,9 @@
244249
core_api_views.UserAPIRegistrationViewSet.as_view({'post': 'post'}),
245250
name='api_register'
246251
),
252+
path('api/v2/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
253+
path('api/v2/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
254+
path('api/v2/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
247255

248256
# Others
249257
path(

0 commit comments

Comments
 (0)