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
|
||||
|
||||
@admin.register(CustomUser)
|
||||
class CustomUserAdmin(admin.ModelAdmin):
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
add_form = CustomUserCreationForm
|
||||
|
||||
fieldsets = (
|
||||
|
|
@ -20,3 +20,4 @@ class CustomUserAdmin(admin.ModelAdmin):
|
|||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
custom = models.CharField(max_length=1000, default='')
|
||||
phone = models.CharField(max_length=20, default='')
|
||||
address = models.CharField(max_length=150, default='')
|
||||
custom = models.CharField(max_length=1000, blank=True)
|
||||
phone = models.CharField(max_length=20, blank=True)
|
||||
address = models.CharField(max_length=150, blank=True)
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
from django.http import JsonResponse
|
||||
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 import status
|
||||
from .serializer import UserSerializer
|
||||
from .models import CustomUser
|
||||
|
||||
@api_view(['GET', 'POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def user_list(request):
|
||||
|
||||
if request.method == 'GET':
|
||||
|
|
@ -19,8 +21,10 @@ def user_list(request):
|
|||
if serializer.is_valid():
|
||||
serializer.save
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@api_view(['GET', 'PUT', 'DELETE'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def user_detail(request, id):
|
||||
|
||||
try:
|
||||
|
|
@ -35,7 +39,7 @@ def user_detail(request, id):
|
|||
elif request.method == 'PUT':
|
||||
serializer = UserSerializer(user, data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer().save()
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ CORS_ALLOWED_ORIGINS = [
|
|||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
|
@ -40,14 +39,12 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework.authtoken',
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'users',
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'users.CustomUser'
|
||||
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
|
|
@ -57,7 +54,6 @@ MIDDLEWARE = [
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware'
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'videoflix.urls'
|
||||
|
|
@ -80,7 +76,6 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = 'videoflix.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# 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
|
||||
|
||||
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'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.urls import path, include
|
||||
from users import views
|
||||
from auth.views import LoginView
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('users/', views.user_list),
|
||||
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 &&
|
||||
!isUserEmailValid(authData.mail.toLowerCase())) {
|
||||
<p>This is not a valid email format</p>
|
||||
}}
|
||||
}} @if ((errorService.error$ | async)) {
|
||||
<p>{{ errorService.error$ | async }}</p>
|
||||
}
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component';
|
||||
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({
|
||||
selector: 'app-login',
|
||||
standalone: true,
|
||||
imports: [BtnLargeComponent, FormsModule, RouterLink],
|
||||
imports: [BtnLargeComponent, CommonModule, FormsModule, RouterLink],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss',
|
||||
})
|
||||
|
|
@ -18,17 +22,35 @@ export class LoginComponent {
|
|||
send: false,
|
||||
};
|
||||
|
||||
constructor() {}
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
public errorService: ErrorService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
isUserEmailValid(emailValue: string) {
|
||||
const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
|
||||
return emailRegex.test(emailValue);
|
||||
}
|
||||
|
||||
onSubmit(ngForm: NgForm) {
|
||||
async onSubmit(ngForm: NgForm) {
|
||||
if (ngForm.submitted && ngForm.form.valid) {
|
||||
console.log(this.authData);
|
||||
const body = {
|
||||
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 { Observable } from 'rxjs';
|
||||
import { lastValueFrom, Observable } from 'rxjs';
|
||||
import { environment } from '../environments/environment.development';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
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> {
|
||||
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