diff --git a/backend/auth_app/views.py b/backend/auth_app/views.py index 7c3cb25..358419f 100644 --- a/backend/auth_app/views.py +++ b/backend/auth_app/views.py @@ -1,7 +1,7 @@ from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework import status, authentication +from rest_framework import status, authentication, permissions from rest_framework.authtoken.models import Token from rest_framework.authtoken.views import ObtainAuthToken from django.contrib.auth import authenticate @@ -36,6 +36,17 @@ class LoginView(APIView): token, created = Token.objects.get_or_create(user=user) return Response({'token': token.key}, status=status.HTTP_200_OK) +class LogoutView(APIView): + authentication_classes = [authentication.TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + try: + request.user.auth_token.delete() + except Token.DoesNotExist: + return Response({'detail': 'Token not found.'}, status=status.HTTP_400_BAD_REQUEST) + return Response({'detail': 'Successfully logged out.'}, status=status.HTTP_200_OK) + class RegisterView(APIView): def post(self, request): email = request.data.get('email') @@ -116,7 +127,7 @@ class ForgotPasswordView(APIView): message = EmailMultiAlternatives( subject='Reset your Password', - body=strip_tags(html_body), # Plain text fallback + body=strip_tags(html_body), from_email=settings.DEFAULT_FROM_EMAIL, to=[user.email] ) @@ -142,7 +153,8 @@ class ChangePasswordView(APIView): class AuthView(ObtainAuthToken): authentication_classes = [authentication.TokenAuthentication] - + permission_classes = [permissions.IsAuthenticated] + def get(self, request): if request.user.is_authenticated: return Response(request.user.id, status=status.HTTP_200_OK) diff --git a/backend/videoflix/urls.py b/backend/videoflix/urls.py index a437a64..0220a40 100644 --- a/backend/videoflix/urls.py +++ b/backend/videoflix/urls.py @@ -21,6 +21,7 @@ from django.conf.urls.static import static from user_app import views as user_views from auth_app.views import ( LoginView, + LogoutView, RegisterView, VerifyEmailView, AuthView, @@ -49,6 +50,7 @@ urlpatterns = [ # Authentication URLs path('auth/', AuthView.as_view(), name='auth_view'), path('auth/login/', LoginView.as_view(), name='login'), + path('auth/logout/', LogoutView.as_view(), name='logout'), path('auth/register/', RegisterView.as_view(), name='register'), path('auth/verify-email/', VerifyEmailView.as_view(), name='verify_email'), path('auth/forgot-password/', ForgotPasswordView.as_view(), name='forgot_password'), diff --git a/frontend/src/app/services/auth.service.ts b/frontend/src/app/services/auth.service.ts index 8776c7d..65b96bb 100644 --- a/frontend/src/app/services/auth.service.ts +++ b/frontend/src/app/services/auth.service.ts @@ -40,6 +40,13 @@ export class AuthService { this.storeAuthToken(data.token, storage); } + async logout() { + const headers = this.getAuthHeaders(); + await lastValueFrom( + this.http.post(`${environment.baseUrl}/auth/logout/`, null, { headers }) + ); + } + async verifyEmail(body: any) { await lastValueFrom( this.http.post(`${environment.baseUrl}/auth/verify-email/`, body) @@ -84,10 +91,9 @@ export class AuthService { } private getAuthHeaders(): HttpHeaders { - let authToken = localStorage.getItem('authToken'); - if (!authToken) { - authToken = sessionStorage.getItem('authToken'); - } + let authToken = + localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); + return new HttpHeaders({ Authorization: `Token ${authToken}`, }); diff --git a/frontend/src/app/services/movie.service.ts b/frontend/src/app/services/movie.service.ts index 3debafb..5d41fff 100644 --- a/frontend/src/app/services/movie.service.ts +++ b/frontend/src/app/services/movie.service.ts @@ -2,6 +2,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { BehaviorSubject, lastValueFrom, Observable } from 'rxjs'; import { environment } from '../../environments/environment'; +import { Router } from '@angular/router'; @Injectable({ providedIn: 'root', @@ -9,7 +10,7 @@ import { environment } from '../../environments/environment'; export class MovieService { private movieCache: { [key: number]: { [resolution: string]: boolean } } = {}; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient, private router: Router) {} getAllMovies(): Promise { const url = environment.baseUrl + '/video/'; @@ -30,15 +31,24 @@ export class MovieService { } private getAuthHeaders(): HttpHeaders { - let authToken = localStorage.getItem('authToken'); + let authToken = + localStorage.getItem('authToken') || sessionStorage.getItem('authToken'); + if (!authToken) { - authToken = sessionStorage.getItem('authToken'); + this.logout(); } + return new HttpHeaders({ Authorization: `Token ${authToken}`, }); } + private logout(): void { + localStorage.clear(); + sessionStorage.clear(); + this.router.navigate(['/']); + } + /** * Checks if a movie is uploaded in multiple resolutions. * @@ -90,6 +100,9 @@ export class MovieService { observer.complete(); }, (error) => { + if (error.status === 401) { + this.logout(); + } observer.next( this.movieCache[videoID] || { '360': false, diff --git a/frontend/src/app/shared/components/header/header.component.ts b/frontend/src/app/shared/components/header/header.component.ts index 7c1b71e..f15a570 100644 --- a/frontend/src/app/shared/components/header/header.component.ts +++ b/frontend/src/app/shared/components/header/header.component.ts @@ -1,6 +1,8 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { BtnLargeComponent } from '../buttons/btn-large/btn-large.component'; import { Router, RouterLink } from '@angular/router'; +import { AuthService } from '../../../services/auth.service'; +import { UserService } from '../../../services/user.service'; @Component({ selector: 'app-header', @@ -13,15 +15,20 @@ export class HeaderComponent { @Input() showFullLogo: boolean = true; @Output() moviesChange = new EventEmitter(); - constructor(private router: Router) {} + constructor(private router: Router, private authService: AuthService) {} backToOverview(newMovies: any[]) { this.moviesChange.emit(newMovies); } - logout() { - localStorage.clear(); - sessionStorage.clear(); - this.router.navigate(['/']); + async logout() { + try { + await this.authService.logout(); + localStorage.clear(); + sessionStorage.clear(); + this.router.navigate(['/']); + } catch (error) { + console.log(error); + } } }