Skip to content

Commit

Permalink
feat[bckend-proxy-middleware]: Added Nginx as a reverse proxy for API…
Browse files Browse the repository at this point in the history
… requests and implemented a middleware to filter API requests based on user roles.
  • Loading branch information
shikharpa committed Apr 11, 2024
1 parent d4b819d commit f913c6b
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 23 deletions.
13 changes: 11 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ services:
- |
poetry run python manage.py migrate
poetry run python manage.py runserver 0.0.0.0:8000
ports:
- 8000:8000
volumes:
- ./services/api/:/usr/src/app/
ports:
- 8000:8000
environment:
- SECRET_KEY=kalvisquad
- DATABASE_NAME=kalvi
Expand Down Expand Up @@ -40,5 +40,14 @@ services:
- POSTGRES_DB=kalvi
redis:
image: redis:alpine
proxy:
build:
context: ./nginx
dockerfile: Dockerfile
restart: unless-stopped
ports:
- 80:80
depends_on:
- api
volumes:
postgres_data:
11 changes: 11 additions & 0 deletions nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM nginx:1.25.4-alpine
RUN apk add --no-cache openssl gettext
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf.dev /etc/nginx/nginx.conf.template
COPY env.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
CMD ["/docker-entrypoint.sh"]




4 changes: 4 additions & 0 deletions nginx/env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh

envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/conf.d/default.conf
exec nginx -g 'daemon off;'
21 changes: 21 additions & 0 deletions nginx/nginx.conf.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
events {
}

http {
sendfile on;

server {
listen 80;
root /www/data/;
access_log /var/log/nginx/access.log;

add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "interest-cohort=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location /api/ {
proxy_pass http://api:8000/api/;
}
}
}
28 changes: 14 additions & 14 deletions services/api/kalvi/api/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,69 @@

urlpatterns = [
path(
"sign-up/",
"common/sign-up/",
SignUpEndPoint.as_view(),
name="kalvi-sign-up",
),
path(
"sign-in/",
"common/sign-in/",
SignInEndPoint.as_view(),
name="kalvi-sign-in",
),
path(
"user/",
"common/user/",
UserProfileView.as_view(),
name="user-detail",
),
path(
"update-password/",
"common/update-password/",
UserChangePasswordView.as_view(),
name="password-update",
),
path(
"send-reset-password-email/",
"common/send-reset-password-email/",
SendPasswordResetEmailView.as_view(),
name="send-reset-email",
),
path(
"reset-password/<str:uid>/<str:token>/",
"common/reset-password/<str:uid>/<str:token>/",
UserPasswordResetView.as_view(),
name="reset-password-through-mail",
),
path(
"google/",
"common/google/auth/",
GoogleSocialAuthView.as_view(),
name="google-sign-in"
),
path(
"github/",
"common/github/auth/",
GithubSocialAuthView.as_view(),
name="github-sign-in"
),
path('token/refresh/',
path('common/token/refresh/',
TokenRefreshView.as_view(),
name='token_refresh'
),
path("sign-out/",
path("common/sign-out/",
SignOutEndpoint.as_view(),
name="sign-out"
),
path(
"magic-generate/",
"common/magic-generate/",
MagicGenerateEndpoint.as_view(),
name="magic-generate",
),
path(
"magic-sign-in/",
"common/magic-sign-in/",
MagicSignInEndpoint.as_view(),
name="magic-sign-in"
),
path(
'profile/',
'common/profile/',
ProfileAPIView.as_view(),
name='profile'
),
path('organization-settings/',
path('admin/organization-settings/',
OrganizationSettingsAPIView.as_view(),
name='organization-settings')
]
18 changes: 11 additions & 7 deletions services/api/kalvi/api/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework_simplejwt.views import TokenRefreshView as SimpleJWTTokenRefreshView
from django.contrib.auth import authenticate
from db.renderer import UserRenderer
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework.permissions import AllowAny
from django.utils.encoding import smart_str
Expand All @@ -26,11 +26,15 @@

# Generate stateless token for User
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
refresh = RefreshToken.for_user(user)
refresh['email'] = user.email
refresh['is_superuser'] = user.is_superuser
refresh['is_staff'] = user.is_staff
access_token = refresh.access_token
return {
'refresh': str(refresh),
'access': str(access_token),
}

# Generate name from email
def generate_name_from_email(email):
Expand Down Expand Up @@ -67,7 +71,7 @@ def post(self, request, format=None):
token = get_tokens_for_user(user)
return Response({'token': token, 'message': 'user registered'}, status=status.HTTP_201_CREATED)
except Exception: # Catch potential errors during user creation
return Response({"error": "Registration failed. Please try again later."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({"error": "Registration failed. Please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Expand Down
27 changes: 27 additions & 0 deletions services/api/kalvi/middlewares/AdminUserMiddleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# custom_middleware.py
from rest_framework import status
from django.http import JsonResponse
from rest_framework_simplejwt.tokens import UntypedToken
from db.models import User
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError

class AdminUserMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
auth_header = request.headers.get('Authorization')
if not auth_header:
return self.get_response(request)
try:
token = UntypedToken(token = auth_header.split()[1])
except (InvalidToken, TokenError):
return JsonResponse({'error': "Invalid or expired token"}, status=status.HTTP_401_UNAUTHORIZED)
is_superuser = token.payload.get('is_superuser')
is_admin = '/api/admin/' in request.path
is_user = '/api/user/' in request.path
# If the user is trying to access the wrong API, return Forbidden
if is_admin and not is_superuser:
return JsonResponse({'error': "Access is not allowed"}, status=status.HTTP_403_FORBIDDEN)
if is_user and is_superuser:
return JsonResponse({'error': "Access is not allowed"}, status=status.HTTP_403_FORBIDDEN)
return self.get_response(request)
4 changes: 4 additions & 0 deletions services/api/kalvi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"kalvi.middlewares.AdminUserMiddleware.AdminUserMiddleware",
]

ROOT_URLCONF = "kalvi.urls"
Expand Down Expand Up @@ -152,6 +153,9 @@
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticated",
),
"DEFAULT_RENDERER_CLASSES": (
"rest_framework.renderers.JSONRenderer",
),
}

AUTH_USER_MODEL = "db.User"
Expand Down

0 comments on commit f913c6b

Please sign in to comment.