feat: implement server-side logout by deleting token to invalidate user session

This commit is contained in:
Chneemann 2025-04-22 06:46:54 +02:00
parent d77aee2b9f
commit 8a2ed1a733
5 changed files with 55 additions and 15 deletions

View file

@ -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,6 +153,7 @@ class ChangePasswordView(APIView):
class AuthView(ObtainAuthToken):
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
if request.user.is_authenticated:

View file

@ -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'),

View file

@ -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}`,
});

View file

@ -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,

View file

@ -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);
}
}
}