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