-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added up auth module * init frontend * added middleware/protected routes
- Loading branch information
1 parent
1feaac8
commit a4d3a78
Showing
41 changed files
with
11,024 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
*.log* | ||
*.pyc | ||
.vscode | ||
.vscode | ||
.venv |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.contrib import admin | ||
|
||
# Register your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class AuthenticationConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "authentication" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Generated by Django 4.1.4 on 2023-06-20 18:23 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("auth", "0012_alter_user_first_name_max_length"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="User", | ||
fields=[ | ||
( | ||
"id", | ||
models.BigAutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
("password", models.CharField(max_length=128, verbose_name="password")), | ||
( | ||
"last_login", | ||
models.DateTimeField( | ||
blank=True, null=True, verbose_name="last login" | ||
), | ||
), | ||
( | ||
"is_superuser", | ||
models.BooleanField( | ||
default=False, | ||
help_text="Designates that this user has all permissions without explicitly assigning them.", | ||
verbose_name="superuser status", | ||
), | ||
), | ||
( | ||
"username", | ||
models.CharField(db_index=True, max_length=255, unique=True), | ||
), | ||
( | ||
"email", | ||
models.EmailField(db_index=True, max_length=255, unique=True), | ||
), | ||
("is_staff", models.BooleanField(default=False)), | ||
("is_verified", models.BooleanField(default=False)), | ||
("created_at", models.DateTimeField(auto_now_add=True)), | ||
("updated_at", models.DateTimeField(auto_now=True)), | ||
("avatar", models.CharField(default="", max_length=225)), | ||
( | ||
"groups", | ||
models.ManyToManyField( | ||
blank=True, | ||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", | ||
related_name="user_set", | ||
related_query_name="user", | ||
to="auth.group", | ||
verbose_name="groups", | ||
), | ||
), | ||
( | ||
"user_permissions", | ||
models.ManyToManyField( | ||
blank=True, | ||
help_text="Specific permissions for this user.", | ||
related_name="user_set", | ||
related_query_name="user", | ||
to="auth.permission", | ||
verbose_name="user permissions", | ||
), | ||
), | ||
], | ||
options={ | ||
"abstract": False, | ||
}, | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin | ||
from django.db import models | ||
from rest_framework_simplejwt.tokens import RefreshToken | ||
|
||
|
||
class UserManager(BaseUserManager): | ||
def create_user(self, username, email, password=None): | ||
if username is None: | ||
raise TypeError("Users should have a username") | ||
if email is None: | ||
raise TypeError("Users should have a Email") | ||
|
||
user = self.model( | ||
username=username, | ||
email=self.normalize_email(email), | ||
) | ||
user.set_password(password) | ||
user.save() | ||
return user | ||
|
||
def create_superuser(self, username, email, password=None): | ||
if password is None: | ||
raise TypeError("Password should not be none") | ||
|
||
user = self.create_user(username, email, password) | ||
user.is_superuser = True | ||
user.is_staff = True | ||
user.save() | ||
return user | ||
|
||
|
||
class User(AbstractBaseUser, PermissionsMixin): | ||
username = models.CharField(max_length=255, unique=True, db_index=True) | ||
email = models.EmailField(max_length=255, unique=True, db_index=True) | ||
is_staff = models.BooleanField(default=False) | ||
is_verified = models.BooleanField(default=False) | ||
created_at = models.DateTimeField(auto_now_add=True) | ||
updated_at = models.DateTimeField(auto_now=True) | ||
avatar = models.CharField(default="", max_length=225) | ||
|
||
USERNAME_FIELD = "email" | ||
REQUIRED_FIELDS = ["username"] | ||
|
||
objects = UserManager() | ||
|
||
def __str__(self): | ||
return self.email | ||
|
||
def tokens(self): | ||
refresh = RefreshToken.for_user(self) | ||
return {"refresh": str(refresh), "access": str(refresh.access_token)} | ||
|
||
|
||
# Create your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
from django.contrib import auth | ||
from django.contrib.auth import logout | ||
from rest_framework import serializers as rfs | ||
from rest_framework.exceptions import AuthenticationFailed | ||
from rest_framework_simplejwt.tokens import TokenError | ||
|
||
from .models import User | ||
|
||
|
||
class RegisterSerializer(rfs.ModelSerializer): | ||
password = rfs.CharField(max_length=68, min_length=6, write_only=True) | ||
|
||
default_error_messages = {"username": "The username should only contain alphanumeric characters"} | ||
|
||
class Meta: | ||
model = User | ||
fields = ["email", "username", "password"] | ||
|
||
def validate(self, attrs): | ||
email = attrs.get("email", "") | ||
username = attrs.get("username", "") | ||
|
||
user_filtered_by_email = User.objects.filter(email=email).first() | ||
if user_filtered_by_email: | ||
raise rfs.ValidationError("User with that email already exists") | ||
|
||
user_filtered_by_username = User.objects.filter(username=username).first() | ||
if user_filtered_by_username: | ||
raise rfs.ValidationError("User with that username already exists") | ||
|
||
if not username.isalnum(): | ||
raise rfs.ValidationError(self.default_error_messages) | ||
return attrs | ||
|
||
def create(self, validated_data): | ||
return User.objects.create_user(**validated_data) | ||
|
||
|
||
class LoginSerializer(rfs.ModelSerializer): | ||
email = rfs.EmailField(max_length=255, min_length=3) | ||
password = rfs.CharField(max_length=68, min_length=6, write_only=True) | ||
|
||
tokens = rfs.SerializerMethodField() | ||
|
||
def get_tokens(self, obj): | ||
user = User.objects.get(email=obj["email"]) | ||
|
||
return {"refresh": user.tokens()["refresh"], "access": user.tokens()["access"]} | ||
|
||
class Meta: | ||
model = User | ||
fields = [ | ||
"email", | ||
"password", | ||
"tokens", | ||
] | ||
|
||
def validate(self, attrs): | ||
email = attrs.get("email", "") | ||
password = attrs.get("password", "") | ||
|
||
user = auth.authenticate(email=email, password=password) | ||
|
||
if not user: | ||
raise AuthenticationFailed("Invalid credentials, try again") | ||
|
||
return {"email": user.email, "username": user.username, "tokens": user.tokens} | ||
|
||
|
||
class LogoutSerializer(rfs.Serializer): | ||
default_error_message = {"bad_token": ("Token is expired or invalid")} | ||
|
||
def validate(self, attrs): | ||
user = self.context["request"].user | ||
self.tokens = user.tokens() | ||
return attrs | ||
|
||
def save(self): | ||
try: | ||
logout(self.context["request"]) | ||
except TokenError: | ||
self.fail("bad_token") | ||
|
||
|
||
class UserSerializer(rfs.ModelSerializer): | ||
class Meta: | ||
model = User | ||
fields = ( | ||
"id", | ||
"email", | ||
"username", | ||
"created_at", | ||
"updated_at", | ||
"avatar", | ||
"is_staff", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from django.test import TestCase | ||
|
||
# Create your tests here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.urls import path | ||
from rest_framework_simplejwt.views import TokenRefreshView | ||
|
||
from .views import LoginAPIView, LogoutAPIView, RegisterView | ||
|
||
urlpatterns = [ | ||
path("login", LoginAPIView.as_view(), name="login"), | ||
path("register", RegisterView.as_view(), name="register"), | ||
path("logout", LogoutAPIView.as_view(), name="logout"), | ||
path("token/refresh", TokenRefreshView.as_view(), name="token_refresh"), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import logging | ||
import os | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.http import HttpResponsePermanentRedirect | ||
from django.shortcuts import render | ||
from rest_framework import generics, permissions, status | ||
from rest_framework.exceptions import AuthenticationFailed | ||
from rest_framework.response import Response | ||
|
||
from .serializers import LoginSerializer, LogoutSerializer, RegisterSerializer, UserSerializer | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class CustomRedirect(HttpResponsePermanentRedirect): | ||
allowed_schemes = [os.environ.get("APP_SCHEME"), "http", "https"] | ||
|
||
|
||
class RegisterView(generics.GenericAPIView): | ||
|
||
serializer_class = RegisterSerializer | ||
authentication_classes = [] | ||
permission_classes = [] | ||
|
||
def post(self, request): | ||
user = request.data | ||
serializer = self.serializer_class(data=user) | ||
serializer.is_valid(raise_exception=True) | ||
serializer.save() | ||
user_data = serializer.data | ||
user = User.objects.get(email=user_data["email"]) | ||
|
||
return Response( | ||
{ | ||
"status": "successful", | ||
}, | ||
status=status.HTTP_201_CREATED, | ||
) | ||
|
||
|
||
class LoginAPIView(generics.GenericAPIView): | ||
serializer_class = LoginSerializer | ||
authentication_classes = [] | ||
permission_classes = [] | ||
|
||
def post(self, request): | ||
serializer = self.serializer_class(data=request.data) | ||
serializer.is_valid(raise_exception=True) | ||
data = serializer.data | ||
data["username"] = serializer.validated_data["username"] | ||
return Response(data, status=status.HTTP_200_OK) | ||
|
||
|
||
class LogoutAPIView(generics.GenericAPIView): | ||
serializer_class = LogoutSerializer | ||
permission_classes = (permissions.IsAuthenticated,) | ||
|
||
def post(self, request): | ||
|
||
serializer = self.serializer_class(data=request.data, context={"request": request}) | ||
serializer.is_valid(raise_exception=True) | ||
serializer.save() | ||
|
||
return Response({"status": "successful"}, status=status.HTTP_200_OK) | ||
|
||
|
||
class MeAPIView(generics.ListAPIView): | ||
serializer_class = UserSerializer | ||
permission_classes = (permissions.IsAuthenticated,) | ||
|
||
def get_queryset(self): | ||
user = self.request.user | ||
return User.objects.filter(id=user.id) | ||
|
||
|
||
# Create your views here. |
Oops, something went wrong.