added & configure auth login, environments & error.service
This commit is contained in:
parent
53c2caa45e
commit
b8a6c95910
20 changed files with 194 additions and 38 deletions
0
backend/videoflix/auth/__init__.py
Normal file
0
backend/videoflix/auth/__init__.py
Normal file
3
backend/videoflix/auth/admin.py
Normal file
3
backend/videoflix/auth/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
backend/videoflix/auth/apps.py
Normal file
6
backend/videoflix/auth/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'auth'
|
||||||
13
backend/videoflix/auth/custom_backend.py
Normal file
13
backend/videoflix/auth/custom_backend.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
class EmailBackend(ModelBackend):
|
||||||
|
def authenticate(self, request, email=None, password=None, **kwargs):
|
||||||
|
try:
|
||||||
|
user = UserModel.objects.get(email=email)
|
||||||
|
if user.check_password(password):
|
||||||
|
return user
|
||||||
|
except UserModel.DoesNotExist:
|
||||||
|
return None
|
||||||
0
backend/videoflix/auth/migrations/__init__.py
Normal file
0
backend/videoflix/auth/migrations/__init__.py
Normal file
3
backend/videoflix/auth/models.py
Normal file
3
backend/videoflix/auth/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
17
backend/videoflix/auth/serializer.py
Normal file
17
backend/videoflix/auth/serializer.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
class LoginSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField(required=True)
|
||||||
|
password = serializers.CharField(write_only=True, required=True)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
email = data.get('email')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
raise serializers.ValidationError("Email is required.")
|
||||||
|
|
||||||
|
if not password:
|
||||||
|
raise serializers.ValidationError("Password is required.")
|
||||||
|
|
||||||
|
return data
|
||||||
3
backend/videoflix/auth/tests.py
Normal file
3
backend/videoflix/auth/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
27
backend/videoflix/auth/views.py
Normal file
27
backend/videoflix/auth/views.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from .serializer import LoginSerializer
|
||||||
|
|
||||||
|
class LoginView(APIView):
|
||||||
|
serializer_class = LoginSerializer
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
email = serializer.validated_data['email']
|
||||||
|
password = serializer.validated_data['password']
|
||||||
|
user = authenticate(request, email=email, password=password)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
return self._create_token_response(user)
|
||||||
|
return Response({'detail': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def _create_token_response(self, user):
|
||||||
|
token, created = Token.objects.get_or_create(user=user)
|
||||||
|
return Response({token.key}, status=status.HTTP_200_OK)
|
||||||
|
|
@ -4,7 +4,7 @@ from .forms import CustomUserCreationForm
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
@admin.register(CustomUser)
|
@admin.register(CustomUser)
|
||||||
class CustomUserAdmin(admin.ModelAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
add_form = CustomUserCreationForm
|
add_form = CustomUserCreationForm
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|
@ -16,7 +16,8 @@ class CustomUserAdmin(admin.ModelAdmin):
|
||||||
'custom',
|
'custom',
|
||||||
'phone',
|
'phone',
|
||||||
'address',
|
'address',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
class CustomUser(AbstractUser):
|
class CustomUser(AbstractUser):
|
||||||
custom = models.CharField(max_length=1000, default='')
|
custom = models.CharField(max_length=1000, blank=True)
|
||||||
phone = models.CharField(max_length=20, default='')
|
phone = models.CharField(max_length=20, blank=True)
|
||||||
address = models.CharField(max_length=150, default='')
|
address = models.CharField(max_length=150, blank=True)
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from .serializer import UserSerializer
|
from .serializer import UserSerializer
|
||||||
from .models import CustomUser
|
from .models import CustomUser
|
||||||
|
|
||||||
@api_view(['GET', 'POST'])
|
@api_view(['GET', 'POST'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
def user_list(request):
|
def user_list(request):
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
|
@ -19,8 +21,10 @@ def user_list(request):
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save
|
serializer.save
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@api_view(['GET', 'PUT', 'DELETE'])
|
@api_view(['GET', 'PUT', 'DELETE'])
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
def user_detail(request, id):
|
def user_detail(request, id):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -35,7 +39,7 @@ def user_detail(request, id):
|
||||||
elif request.method == 'PUT':
|
elif request.method == 'PUT':
|
||||||
serializer = UserSerializer(user, data=request.data)
|
serializer = UserSerializer(user, data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer().save()
|
serializer.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ CORS_ALLOWED_ORIGINS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
|
@ -40,14 +39,12 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework.authtoken',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'users',
|
'users',
|
||||||
]
|
]
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'users.CustomUser'
|
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
|
@ -57,7 +54,6 @@ MIDDLEWARE = [
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'corsheaders.middleware.CorsMiddleware',
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'videoflix.urls'
|
ROOT_URLCONF = 'videoflix.urls'
|
||||||
|
|
@ -80,7 +76,6 @@ TEMPLATES = [
|
||||||
|
|
||||||
WSGI_APPLICATION = 'videoflix.wsgi.application'
|
WSGI_APPLICATION = 'videoflix.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
|
@ -132,3 +127,20 @@ STATIC_URL = 'static/'
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
|
||||||
|
AUTH_EMAIL_VERIFICATION = True
|
||||||
|
AUTH_USER_MODEL = 'users.CustomUser'
|
||||||
|
|
||||||
|
# Rest Framework
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'auth.custom_backend.EmailBackend',
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,14 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
from users import views
|
from users import views
|
||||||
|
from auth.views import LoginView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('users/', views.user_list),
|
path('users/', views.user_list),
|
||||||
path('users/<int:id>/', views.user_detail),
|
path('users/<int:id>/', views.user_detail),
|
||||||
|
path('auth/login/', LoginView.as_view()),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@
|
||||||
} @else { @if (mail.touched && authData.mail.length > 0 &&
|
} @else { @if (mail.touched && authData.mail.length > 0 &&
|
||||||
!isUserEmailValid(authData.mail.toLowerCase())) {
|
!isUserEmailValid(authData.mail.toLowerCase())) {
|
||||||
<p>This is not a valid email format</p>
|
<p>This is not a valid email format</p>
|
||||||
}}
|
}} @if ((errorService.error$ | async)) {
|
||||||
|
<p>{{ errorService.error$ | async }}</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component';
|
import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component';
|
||||||
import { FormsModule, NgForm } from '@angular/forms';
|
import { FormsModule, NgForm } from '@angular/forms';
|
||||||
import { RouterLink } from '@angular/router';
|
import { Router, RouterLink } from '@angular/router';
|
||||||
|
import { AuthService } from '../../../services/auth.service';
|
||||||
|
import { ErrorService } from '../../../services/error.service';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [BtnLargeComponent, FormsModule, RouterLink],
|
imports: [BtnLargeComponent, CommonModule, FormsModule, RouterLink],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.scss',
|
styleUrl: './login.component.scss',
|
||||||
})
|
})
|
||||||
|
|
@ -18,17 +22,35 @@ export class LoginComponent {
|
||||||
send: false,
|
send: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {}
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
public errorService: ErrorService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
isUserEmailValid(emailValue: string) {
|
isUserEmailValid(emailValue: string) {
|
||||||
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
|
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
|
||||||
return emailRegex.test(emailValue);
|
return emailRegex.test(emailValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(ngForm: NgForm) {
|
async onSubmit(ngForm: NgForm) {
|
||||||
if (ngForm.submitted && ngForm.form.valid) {
|
if (ngForm.submitted && ngForm.form.valid) {
|
||||||
console.log(this.authData);
|
const body = {
|
||||||
ngForm.resetForm();
|
email: this.authData.mail,
|
||||||
|
password: this.authData.password,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await this.authService.login(body);
|
||||||
|
ngForm.resetForm();
|
||||||
|
this.router.navigate(['/browse/']);
|
||||||
|
this.errorService.clearError();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof HttpErrorResponse) {
|
||||||
|
this.errorService.setError(error.error.detail);
|
||||||
|
} else {
|
||||||
|
this.errorService.setError('An unknown error has occurred.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
frontend/src/app/environments/environment.development.ts
Normal file
3
frontend/src/app/environments/environment.development.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const environment = {
|
||||||
|
baseUrl: 'http://127.0.0.1:8000/',
|
||||||
|
};
|
||||||
3
frontend/src/app/environments/environment.ts
Normal file
3
frontend/src/app/environments/environment.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const environment = {
|
||||||
|
baseUrl: '',
|
||||||
|
};
|
||||||
|
|
@ -1,12 +1,28 @@
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import {
|
||||||
|
HttpClient,
|
||||||
|
HttpErrorResponse,
|
||||||
|
HttpHeaders,
|
||||||
|
} from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { lastValueFrom, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../environments/environment.development';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(private http: HttpClient) {}
|
errorMsg: string | null = null;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private router: Router) {}
|
||||||
|
|
||||||
|
async login(body: any): Promise<void> {
|
||||||
|
const data = await lastValueFrom(
|
||||||
|
this.http.post<any>(`${environment.baseUrl}/auth/login/`, body)
|
||||||
|
);
|
||||||
|
const authToken = data.toString();
|
||||||
|
localStorage.setItem('authToken', authToken);
|
||||||
|
}
|
||||||
|
|
||||||
getAllUsers(): Observable<any> {
|
getAllUsers(): Observable<any> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
|
|
|
||||||
18
frontend/src/app/services/error.service.ts
Normal file
18
frontend/src/app/services/error.service.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ErrorService {
|
||||||
|
private errorSubject = new BehaviorSubject<string | null>(null);
|
||||||
|
error$ = this.errorSubject.asObservable();
|
||||||
|
|
||||||
|
setError(message: string | null) {
|
||||||
|
this.errorSubject.next(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearError() {
|
||||||
|
this.errorSubject.next(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue