Skip to content
This repository was archived by the owner on Mar 27, 2023. It is now read-only.

Commit 2f6405e

Browse files
committed
Merge branch '49-replace-jwt-with-session-auth' into 'develop'
Resolve "Replace JWT authentication with Session Authentication" Closes #49 See merge request verbose-equals-true/django-postgres-vue-gitlab-ecs!149
2 parents 5487688 + 41308c5 commit 2f6405e

File tree

32 files changed

+369
-211
lines changed

32 files changed

+369
-211
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ stages:
1111
include:
1212
- local: /gitlab-ci/documentation.yml
1313
- local: /gitlab-ci/renovate.yml
14+
- local: /gitlab-ci/aws/cdk.yml
1415
- local: /gitlab-ci/aws/dev.yml
1516
- local: /gitlab-ci/aws/app.yml
1617

awscdk/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from aws_cdk import core
55

6-
from awscdk.cdk_app_root import ApplicationStack
6+
from awscdk.app_stack import ApplicationStack
77

88
# naming conventions, also used for ACM certs, DNS Records, resource naming
99
# Dynamically generated resource names created in CDK are used in GitLab CI

awscdk/awscdk/cdk_app_root.py renamed to awscdk/awscdk/app_stack.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from static_site_bucket import StaticSiteStack
1616
from flower import FlowerServiceStack
1717
from celery_autoscaling import CeleryAutoscalingStack
18+
from bastion_host import BastionHost
1819

1920
from backend import BackendServiceStack
2021
from backend_tasks import BackendTasksStack
@@ -80,7 +81,9 @@ def __init__(
8081

8182
# image used for all django containers: gunicorn, daphne, celery, beat
8283
self.image = ecs.AssetImage(
83-
"./backend", file="scripts/prod/Dockerfile", target="production",
84+
"./backend",
85+
file="scripts/prod/Dockerfile",
86+
target="production",
8487
)
8588

8689
self.variables = Variables(
@@ -110,3 +113,6 @@ def __init__(
110113

111114
# migrate, collectstatic, createsuperuser
112115
self.backend_tasks = BackendTasksStack(self, "BackendTasksStack")
116+
117+
# bastion host
118+
self.bastion_host = BastionHost(self, "BastionHost")

awscdk/awscdk/backend_tasks.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import os
22

3-
from aws_cdk import core, aws_ecs as ecs, aws_cloudformation as cloudformation
3+
from aws_cdk import (
4+
core,
5+
aws_ecs as ecs,
6+
aws_cloudformation as cloudformation,
7+
aws_logs as logs,
8+
)
49

510
# These tasks are executed from manual GitLab CI jobs. The cluster is
611
# specified with:
@@ -9,9 +14,16 @@
914

1015

1116
class BackendTasksStack(cloudformation.NestedStack):
12-
def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
17+
def __init__(
18+
self,
19+
scope: core.Construct,
20+
id: str,
21+
**kwargs,
22+
) -> None:
1323
super().__init__(
14-
scope, id, **kwargs,
24+
scope,
25+
id,
26+
**kwargs,
1527
)
1628

1729
# migrate
@@ -28,7 +40,10 @@ def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
2840
environment=scope.variables.regular_variables,
2941
secrets=scope.variables.secret_variables,
3042
command=["python3", "manage.py", "migrate", "--no-input"],
31-
logging=ecs.LogDrivers.aws_logs(stream_prefix="MigrateCommand"),
43+
logging=ecs.LogDrivers.aws_logs(
44+
stream_prefix="MigrateCommand",
45+
log_retention=logs.RetentionDays.ONE_DAY,
46+
),
3247
)
3348

3449
# collectstatic
@@ -79,6 +94,7 @@ def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
7994
),
8095
command=["python3", "manage.py", "create_default_user"],
8196
logging=ecs.LogDrivers.aws_logs(
82-
stream_prefix="CreateSuperuserCommand"
97+
stream_prefix="CreateSuperuserCommand",
98+
log_retention=logs.RetentionDays.ONE_DAY,
8399
),
84100
)

awscdk/awscdk/bastion_host.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
3+
from aws_cdk import (
4+
aws_autoscaling as autoscaling,
5+
aws_cloudformation as cloudformation,
6+
aws_ec2 as ec2,
7+
aws_ecs as ecs,
8+
core,
9+
)
10+
11+
12+
class BastionHost(cloudformation.NestedStack):
13+
def __init__(
14+
self,
15+
scope: core.Construct,
16+
id: str,
17+
**kwargs,
18+
) -> None:
19+
super().__init__(
20+
scope,
21+
id,
22+
**kwargs,
23+
)
24+
25+
# add ingress rule on port 22 for SSH
26+
ec2.SecurityGroup.from_security_group_id(
27+
self,
28+
"DefaultSecurityGroupForIngress",
29+
scope.vpc.vpc_default_security_group,
30+
).add_ingress_rule(
31+
ec2.Peer.any_ipv4(),
32+
ec2.Port.tcp(22),
33+
)
34+
35+
self.asg = autoscaling.AutoScalingGroup(
36+
self,
37+
"AutoScalingGroup",
38+
instance_type=ec2.InstanceType("t2.micro"),
39+
machine_image=ecs.EcsOptimizedAmi(),
40+
security_group=ec2.SecurityGroup.from_security_group_id(
41+
self,
42+
"DefaultSecurityGroupId",
43+
scope.vpc.vpc_default_security_group,
44+
),
45+
associate_public_ip_address=True,
46+
update_type=autoscaling.UpdateType.REPLACING_UPDATE,
47+
desired_capacity=1,
48+
vpc=scope.vpc,
49+
key_name=os.environ.get("KEY_NAME"),
50+
vpc_subnets={'subnet_type': ec2.SubnetType.PUBLIC},
51+
)
52+
53+
self.cluster = scope.cluster
54+
55+
self.cluster.add_auto_scaling_group(self.asg)
56+
57+
self.bastion_host_task = ecs.Ec2TaskDefinition(self, "BastionHostTask")
58+
59+
self.bastion_host_task.add_container(
60+
"BastionHostContainer",
61+
image=scope.image,
62+
command=["/start_prod.sh"],
63+
environment=scope.variables.regular_variables,
64+
memory_reservation_mib=128
65+
# secrets=scope.variables.secret_variables,
66+
)
67+
68+
self.bastion_host_service = ecs.Ec2Service(
69+
self,
70+
"BastionHostService",
71+
task_definition=self.bastion_host_task,
72+
cluster=self.cluster,
73+
)

awscdk/awscdk/cloudfront.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717

1818
class CloudFrontStack(cloudformation.NestedStack):
19-
def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
19+
def __init__(
20+
self,
21+
scope: core.Construct,
22+
id: str,
23+
**kwargs,
24+
) -> None:
2025
super().__init__(scope, id, **kwargs)
2126

2227
s3_domain_prefix = scope.static_site_bucket.bucket_name
@@ -54,7 +59,8 @@ def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
5459
),
5560
behaviors=[
5661
cloudfront.Behavior(
57-
is_default_behavior=True, cached_methods=GET_HEAD,
62+
is_default_behavior=True,
63+
cached_methods=GET_HEAD,
5864
)
5965
],
6066
),

awscdk/awscdk/flower.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111

1212

1313
class FlowerServiceStack(cloudformation.NestedStack):
14-
def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
14+
def __init__(
15+
self,
16+
scope: core.Construct,
17+
id: str,
18+
**kwargs,
19+
) -> None:
1520
super().__init__(
16-
scope, id, **kwargs,
21+
scope,
22+
id,
23+
**kwargs,
1724
)
1825

1926
self.flower_task = ecs.FargateTaskDefinition(self, "FlowerTask")
@@ -26,7 +33,7 @@ def __init__(self, scope: core.Construct, id: str, **kwargs,) -> None:
2633

2734
self.flower_task.add_container(
2835
"FlowerContainer",
29-
image=ecs.ContainerImage.from_registry("mher/flower"),
36+
image=ecs.ContainerImage.from_registry("mher/flower:0.9"),
3037
logging=ecs.LogDrivers.aws_logs(
3138
stream_prefix="FlowerContainer",
3239
log_retention=logs.RetentionDays.ONE_DAY,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.1.2 on 2020-11-17 07:35
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('accounts', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='customuser',
15+
name='first_name',
16+
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
17+
),
18+
]

backend/apps/accounts/urls.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
11
from django.urls import include, path, re_path
2-
from rest_framework_simplejwt.views import (
3-
TokenObtainPairView,
4-
TokenRefreshView,
5-
TokenVerifyView,
6-
)
72

83
from . import views
94

5+
106
urlpatterns = [
11-
re_path(
12-
r"^auth/obtain_token/",
13-
TokenObtainPairView.as_view(),
14-
name="api-jwt-auth",
15-
),
16-
re_path(
17-
r"^auth/refresh_token/",
18-
TokenRefreshView.as_view(),
19-
name="api-jwt-refresh",
7+
path('login-set-cookie/', views.login_set_cookie, name="login-view"),
8+
path('login/', views.login_view, name="login-view"),
9+
path('logout/', views.logout_view, name="logout-view"),
10+
path(
11+
"users/profile/",
12+
views.Profile.as_view(),
13+
name="user-profile",
2014
),
21-
re_path(
22-
r"^auth/verify_token/",
23-
TokenVerifyView.as_view(),
24-
name="api-jwt-verify",
25-
),
26-
path("users/profile/", views.Profile.as_view(), name="user-profile",),
2715
# Social Auth Callbacks
28-
path("social/<backend>/", views.exchange_token, name="social-auth",),
16+
path(
17+
"social/<backend>/",
18+
views.exchange_token,
19+
name="social-auth",
20+
),
2921
]
3022

3123
urlpatterns += [

backend/apps/accounts/views.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import json
2+
13
from django.conf import settings
2-
from django.contrib.auth import get_user_model
4+
from django.contrib.auth import authenticate, get_user_model, login, logout
5+
from django.http import JsonResponse
6+
from django.views.decorators.csrf import ensure_csrf_cookie
7+
from django.views.decorators.http import require_POST
38
from requests.exceptions import HTTPError
49
from rest_framework import permissions, serializers, status
510
from rest_framework.decorators import api_view, permission_classes
@@ -15,6 +20,43 @@
1520
User = get_user_model()
1621

1722

23+
@require_POST
24+
def logout_view(request):
25+
logout(request)
26+
return JsonResponse({"detail": "Logout Successful"})
27+
28+
29+
@ensure_csrf_cookie
30+
def login_set_cookie(request):
31+
"""
32+
`login_view` requires that a csrf cookie be set.
33+
`getCsrfToken` in `auth.js` uses this cookie to
34+
make a request to `login_view`
35+
"""
36+
return JsonResponse({"details": "CSRF cookie set"})
37+
38+
39+
@require_POST
40+
def login_view(request):
41+
"""
42+
This function logs in the user and returns
43+
and HttpOnly cookie, the `sessionid` cookie
44+
"""
45+
data = json.loads(request.body)
46+
email = data.get('email')
47+
password = data.get('password')
48+
if email is None or password is None:
49+
return JsonResponse(
50+
{"errors": {"__all__": "Please enter both username and password"}},
51+
status=400,
52+
)
53+
user = authenticate(email=email, password=password)
54+
if user is not None:
55+
login(request, user)
56+
return JsonResponse({"detail": "Success"})
57+
return JsonResponse({"detail": "Invalid credentials"}, status=400)
58+
59+
1860
def get_tokens_for_user(user):
1961
refresh = RefreshToken.for_user(user)
2062

@@ -29,7 +71,10 @@ class SocialSerializer(serializers.Serializer):
2971
Serializer which accepts an OAuth2 code.
3072
"""
3173

32-
code = serializers.CharField(allow_blank=False, trim_whitespace=True,)
74+
code = serializers.CharField(
75+
allow_blank=False,
76+
trim_whitespace=True,
77+
)
3378

3479

3580
@api_view(http_method_names=["POST"])

0 commit comments

Comments
 (0)