mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-20 06:24:07 +01:00
Add Authenticate API endpoint / Add CustomUser models
React will use this authenticate api endpoint
This commit is contained in:
parent
a82344029f
commit
15b8f1446e
4
backend/assets/accounts/auth.css
Normal file
4
backend/assets/accounts/auth.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
* {
|
||||||
|
outline: 1px red solid;
|
||||||
|
}
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
|||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from decouple import config, Csv
|
from decouple import config, Csv
|
||||||
|
|
||||||
@ -31,7 +32,12 @@ ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='*', cast=Csv())
|
|||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
SITE_ID = 2
|
SITE_ID = 3
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
|
)
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
@ -40,25 +46,52 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'users',
|
'users',
|
||||||
|
'rest_framework',
|
||||||
|
'corsheaders',
|
||||||
|
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'allauth.socialaccount',
|
'allauth.socialaccount',
|
||||||
'allauth.socialaccount.providers.google',
|
'allauth.socialaccount.providers.google',
|
||||||
|
|
||||||
|
'dj_rest_auth',
|
||||||
|
'dj_rest_auth.registration',
|
||||||
|
'rest_framework.authtoken',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
|
||||||
|
],
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
|
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
REST_USE_JWT = True
|
||||||
|
|
||||||
SOCIALACCOUNT_PROVIDERS = {
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
'google': {
|
'google': {
|
||||||
'SCOPE' : [
|
'APP': {
|
||||||
'profile',
|
'client_id': config('GOOGLE_CLIENT_ID'),
|
||||||
'email',
|
'secret': config('GOOGLE_CLIENT_SECRET'),
|
||||||
],
|
'key': ''
|
||||||
'AUTH_PARAMS' : {'access_type' : 'online'}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:8000",
|
||||||
|
"http://127.0.0.1:8000"
|
||||||
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
@ -74,7 +107,9 @@ ROOT_URLCONF = 'core.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [
|
||||||
|
os.path.join(BASE_DIR, 'templates')
|
||||||
|
],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@ -146,10 +181,13 @@ STATIC_URL = 'static/'
|
|||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
'https://www.googleapis.com/auth/userinfo.email',
|
||||||
)
|
'https://www.googleapis.com/auth/userinfo.profile',
|
||||||
|
]
|
||||||
|
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
LOGOUT_REDIRECT_URL = '/'
|
LOGOUT_REDIRECT_URL = '/'
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = "users.CustomUser"
|
||||||
@ -19,6 +19,6 @@ from django.urls import path, include
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('api/', include('users.urls')),
|
||||||
path('accounts/', include('allauth.urls')),
|
path('accounts/', include('allauth.urls')),
|
||||||
path('', include('users.urls')),
|
|
||||||
]
|
]
|
||||||
@ -6,3 +6,5 @@ DB_USER=your_DB_USER
|
|||||||
DB_PASSWORD=your_DB_PASSWORD
|
DB_PASSWORD=your_DB_PASSWORD
|
||||||
DB_HOST=your_DB_HOST
|
DB_HOST=your_DB_HOST
|
||||||
DB_PORT=your_DB_PORT
|
DB_PORT=your_DB_PORT
|
||||||
|
GOOGLE_CLIENT_ID=your_GOOGLE_CLIENT_ID
|
||||||
|
GOOGLE_CLIENT_SECRET=your_GOOGLE_CLIENT_SECRET
|
||||||
@ -1,3 +1,32 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from users.models import CustomUser
|
||||||
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
from django.forms import Textarea
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
# Register your models here.
|
class UserAdminConfig(UserAdmin):
|
||||||
|
model = CustomUser
|
||||||
|
|
||||||
|
search_fields = ('email', 'username', 'first_name',)
|
||||||
|
list_filter = ('email', 'username', 'first_name', 'is_active', 'is_staff')
|
||||||
|
ordering = ('-start_date',)
|
||||||
|
list_display = ('email', 'username', 'first_name', 'is_active', 'is_staff')
|
||||||
|
fieldsets = (
|
||||||
|
(None, {'fields': ('email', 'username', 'first_name',)}),
|
||||||
|
('Permissions', {'fields': ('is_staff', 'is_active')}),
|
||||||
|
('Personal', {'fields': ('about',)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Formfield overrides for 'about' field
|
||||||
|
formfield_overrides = {
|
||||||
|
models.TextField: {'widget': Textarea(attrs={'rows': 20, 'cols': 60})},
|
||||||
|
}
|
||||||
|
|
||||||
|
add_fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'classes': ('wide',),
|
||||||
|
'fields': ('email', 'username', 'first_name', 'password1', 'password2', 'is_active', 'is_staff')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
admin.site.register(CustomUser, UserAdminConfig)
|
||||||
|
|||||||
31
backend/users/managers.py
Normal file
31
backend/users/managers.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.auth.models import BaseUserManager
|
||||||
|
|
||||||
|
class CustomAccountManager(BaseUserManager):
|
||||||
|
def create_superuser(self, email, username, first_name, password, **other_fields):
|
||||||
|
"""
|
||||||
|
Create a superuser with the given email, username, first_name, and password.
|
||||||
|
"""
|
||||||
|
other_fields.setdefault('is_staff', True)
|
||||||
|
other_fields.setdefault('is_superuser', True)
|
||||||
|
other_fields.setdefault('is_active', True)
|
||||||
|
|
||||||
|
if other_fields.get('is_staff') is not True:
|
||||||
|
raise ValueError('Superuser must be assigned to is_staff=True.')
|
||||||
|
if other_fields.get('is_superuser') is not True:
|
||||||
|
raise ValueError('Superuser must be assigned to is_superuser=True.')
|
||||||
|
|
||||||
|
return self.create_user(email, username, first_name, password, **other_fields)
|
||||||
|
|
||||||
|
def create_user(self, email, username, first_name, password, **other_fields):
|
||||||
|
"""
|
||||||
|
Create a user with the given email, username, first_name, and password.
|
||||||
|
"""
|
||||||
|
if not email:
|
||||||
|
raise ValueError(_('You must provide an email address'))
|
||||||
|
|
||||||
|
email = self.normalize_email(email)
|
||||||
|
user = self.model(email=email, username=username, first_name=first_name, **other_fields)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
37
backend/users/migrations/0001_initial.py
Normal file
37
backend/users/migrations/0001_initial.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 4.2.6 on 2023-10-27 13:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CustomUser',
|
||||||
|
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')),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
|
||||||
|
('username', models.CharField(max_length=150, unique=True)),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150)),
|
||||||
|
('start_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('about', models.TextField(blank=True, max_length=500, verbose_name='about')),
|
||||||
|
('is_staff', models.BooleanField(default=False)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,3 +1,28 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
||||||
|
|
||||||
# Create your models here.
|
from .managers import CustomAccountManager
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||||
|
# User fields
|
||||||
|
email = models.EmailField(_('email address'), unique=True)
|
||||||
|
username = models.CharField(max_length=150, unique=True)
|
||||||
|
first_name = models.CharField(max_length=150, blank=True)
|
||||||
|
start_date = models.DateTimeField(default=timezone.now)
|
||||||
|
about = models.TextField(_('about'), max_length=500, blank=True)
|
||||||
|
is_staff = models.BooleanField(default=False)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# Custom manager
|
||||||
|
objects = CustomAccountManager()
|
||||||
|
|
||||||
|
# Fields for authentication
|
||||||
|
USERNAME_FIELD = 'email'
|
||||||
|
REQUIRED_FIELDS = ['username', 'first_name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# String representation of the user
|
||||||
|
return self.username
|
||||||
|
|||||||
39
backend/users/serializers.py
Normal file
39
backend/users/serializers.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import CustomUser
|
||||||
|
|
||||||
|
|
||||||
|
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||||
|
@classmethod
|
||||||
|
def get_token(cls, user):
|
||||||
|
"""
|
||||||
|
Get the token for the user and add custom claims, such as 'username'.
|
||||||
|
"""
|
||||||
|
token = super(MyTokenObtainPairSerializer, cls).get_token(user)
|
||||||
|
token['username'] = user.username
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for CustomUser model.
|
||||||
|
"""
|
||||||
|
email = serializers.EmailField(required=True)
|
||||||
|
username = serializers.CharField(required=True)
|
||||||
|
password = serializers.CharField(min_length=8, write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser
|
||||||
|
fields = ('email', 'username', 'password')
|
||||||
|
extra_kwargs = {'password': {'write_only': True}}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""
|
||||||
|
Create a CustomUser instance with validated data, including password hashing.
|
||||||
|
"""
|
||||||
|
password = validated_data.pop('password', None)
|
||||||
|
instance = self.Meta.model(**validated_data)
|
||||||
|
if password is not None:
|
||||||
|
instance.set_password(password)
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charsets="UTF-8"/>
|
|
||||||
<meta http-equiv="X-UA-compatible" content="IE=edge"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<title>Django Google Sign In</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% load socialaccount %}
|
|
||||||
<h2>Google Login</h2>
|
|
||||||
<a href='{% provider_login_url 'google' %}?next=/'>Login With Google</a>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,7 +1,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from rest_framework_simplejwt import views as jwt_views
|
||||||
|
from .views import ObtainTokenPairWithCustomView, CustomUserCreate, GreetingView, GoogleLogin
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.home),
|
path('user/create/', CustomUserCreate.as_view(), name="create_user"),
|
||||||
path('logout', views.logout_view),
|
path('token/obtain/', jwt_views.TokenObtainPairView.as_view(), name='token_create'),
|
||||||
|
path('token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
|
path('token/custom_obtain/', ObtainTokenPairWithCustomView.as_view(), name='token_create_custom'),
|
||||||
|
path('hello/', GreetingView.as_view(), name='hello_world'),
|
||||||
|
path('dj-rest-auth/google/', GoogleLogin.as_view(), name="google_login"),
|
||||||
]
|
]
|
||||||
@ -1,10 +1,78 @@
|
|||||||
from django.shortcuts import render, redirect
|
"""This module defines API views for authentication, user creation, and a simple hello message."""
|
||||||
from django.contrib.auth import logout
|
|
||||||
|
|
||||||
def home(request):
|
from django.shortcuts import render
|
||||||
return render(request, 'users/home.html')
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
|
||||||
|
from dj_rest_auth.registration.views import SocialLoginView
|
||||||
|
from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
|
||||||
|
|
||||||
|
|
||||||
def logout_view(request):
|
class ObtainTokenPairWithCustomView(APIView):
|
||||||
logout(request)
|
"""
|
||||||
return redirect('/')
|
Custom Token Obtain Pair View.
|
||||||
|
Allows users to obtain access and refresh tokens by providing credentials.
|
||||||
|
"""
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
Issue access and refresh tokens in response to a valid login request.
|
||||||
|
"""
|
||||||
|
serializer = MyTokenObtainPairSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
token = serializer.validated_data
|
||||||
|
return Response(token, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserCreate(APIView):
|
||||||
|
"""
|
||||||
|
Custom User Creation View.
|
||||||
|
Allows users to create new accounts.
|
||||||
|
"""
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
Create a new user account based on the provided data.
|
||||||
|
"""
|
||||||
|
serializer = CustomUserSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
user = serializer.save()
|
||||||
|
if user:
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class GreetingView(APIView):
|
||||||
|
"""
|
||||||
|
Hello World View.
|
||||||
|
Returns a greeting and user information for authenticated users.
|
||||||
|
"""
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
Retrieve a greeting message and user information.
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
user_info = {
|
||||||
|
"username": user.username,
|
||||||
|
}
|
||||||
|
response_data = {
|
||||||
|
"message": "Hello, world!",
|
||||||
|
"user_info": user_info,
|
||||||
|
}
|
||||||
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleLogin(SocialLoginView):
|
||||||
|
"""
|
||||||
|
Google Login View.
|
||||||
|
Handles Google OAuth2 authentication.
|
||||||
|
"""
|
||||||
|
permission_classes = (AllowAny,)
|
||||||
|
adapter_class = GoogleOAuth2Adapter
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
Loading…
Reference in New Issue
Block a user