feat: implement server-side logout by deleting token to invalidate user session
This commit is contained in:
parent
d77aee2b9f
commit
8a2ed1a733
5 changed files with 55 additions and 15 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<any> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<any[]>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue