From 133735e2f7a0474150c027c3b566881a90201506 Mon Sep 17 00:00:00 2001 From: Chneemann Date: Mon, 5 Aug 2024 18:02:51 +0200 Subject: [PATCH] user is now unlocked by his email and token from the email link --- backend/videoflix/auth/views.py | 20 +++++- backend/videoflix/templates/mail.html | 2 +- backend/videoflix/users/admin.py | 2 +- backend/videoflix/users/models.py | 12 ++-- frontend/src/app/app.routes.ts | 1 + .../verify-email/verify-email.component.html | 21 ++++++ .../verify-email/verify-email.component.scss | 8 +++ .../verify-email.component.spec.ts | 23 +++++++ .../verify-email/verify-email.component.ts | 64 +++++++++++++++++++ .../app/components/home/home.component.html | 1 + .../src/app/components/home/home.component.ts | 2 + frontend/src/app/services/auth.service.ts | 14 ++-- 12 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 frontend/src/app/components/auth/verify-email/verify-email.component.html create mode 100644 frontend/src/app/components/auth/verify-email/verify-email.component.scss create mode 100644 frontend/src/app/components/auth/verify-email/verify-email.component.spec.ts create mode 100644 frontend/src/app/components/auth/verify-email/verify-email.component.ts diff --git a/backend/videoflix/auth/views.py b/backend/videoflix/auth/views.py index a55c365..f80407b 100644 --- a/backend/videoflix/auth/views.py +++ b/backend/videoflix/auth/views.py @@ -9,6 +9,7 @@ from django.utils.html import strip_tags from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from users.models import CustomUser +from django.db.models import Q class LoginView(APIView): serializer_class = LoginSerializer @@ -55,7 +56,7 @@ class RegisterView(APIView): merge_data = { 'name': user.username, 'email': user.email, - 'token': user.verify_email + 'token': user.verify_email_token } html_body = render_to_string("mail.html", merge_data) @@ -70,4 +71,19 @@ class RegisterView(APIView): class VerifyEmailView(APIView): def post(self, request): - pass \ No newline at end of file + email = request.data.get('email') + token = request.data.get('token') + + if not email or not token: + return Response({"error": "Email and token are required."}, status=status.HTTP_400_BAD_REQUEST) + + user = CustomUser.objects.filter(Q(email=email) & Q(verify_email_token=token)).first() + if user: + if user.is_active: + return Response({"error": "User has already been activated"}, status=status.HTTP_409_CONFLICT) + + user.is_active = True + user.save() + return Response(status=status.HTTP_200_OK) + else: + return Response({"error": "Invalid email or token."}, status=status.HTTP_400_BAD_REQUEST) diff --git a/backend/videoflix/templates/mail.html b/backend/videoflix/templates/mail.html index a4ab908..240233d 100644 --- a/backend/videoflix/templates/mail.html +++ b/backend/videoflix/templates/mail.html @@ -40,7 +40,7 @@ Thank you for registering with Videoflix. To complete your registration and verify your email address, please click the link below:

- Activate account + Activate account

If you did not create an account with us, please disregard this email.

diff --git a/backend/videoflix/users/admin.py b/backend/videoflix/users/admin.py index 12ef327..d228978 100644 --- a/backend/videoflix/users/admin.py +++ b/backend/videoflix/users/admin.py @@ -13,7 +13,7 @@ class CustomUserAdmin(UserAdmin): 'Individual data', { 'fields': ( - 'verify_email', + 'verify_email_token', ) } ) diff --git a/backend/videoflix/users/models.py b/backend/videoflix/users/models.py index 5378cdc..b8a922d 100644 --- a/backend/videoflix/users/models.py +++ b/backend/videoflix/users/models.py @@ -1,17 +1,17 @@ -import secrets import string -from django.db import models +import secrets from django.contrib.auth.models import AbstractUser +from django.db import models class CustomUser(AbstractUser): - verify_email = models.CharField(max_length=15, blank=True) + verify_email_token = models.CharField(max_length=20, blank=True, null=True) def save(self, *args, **kwargs): - if not self.verify_email: - self.verify_email = self.generate_verification_token() + if not self.verify_email_token: + self.verify_email_token = self.generate_verification_token() super().save(*args, **kwargs) @staticmethod - def generate_verification_token(length=15): + def generate_verification_token(length=20): characters = string.ascii_letters + string.digits return ''.join(secrets.choice(characters) for _ in range(length)) diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index ccf07ff..1ed769b 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -9,6 +9,7 @@ export const routes: Routes = [ { path: 'login', component: HomeComponent }, { path: 'register', component: HomeComponent }, { path: 'forgot-password', component: HomeComponent }, + { path: 'verify-email', component: HomeComponent }, { path: 'imprint', component: ImprintComponent }, { path: 'privacy-policy', component: PrivacyPolicyComponent }, { path: 'browse', component: BrowseComponent }, diff --git a/frontend/src/app/components/auth/verify-email/verify-email.component.html b/frontend/src/app/components/auth/verify-email/verify-email.component.html new file mode 100644 index 0000000..5de9b15 --- /dev/null +++ b/frontend/src/app/components/auth/verify-email/verify-email.component.html @@ -0,0 +1,21 @@ +
+
+
Verify Email
+
+ @if (verified) { +

Your email has been successfully verified! You can now log in.

+ To the login + } @else { +

+ There was an issue verifying your email.
Please try again or + contact support for assistance. +

+ } +
+
+
+

{{ errors["error"] }}

+
+
+
+
diff --git a/frontend/src/app/components/auth/verify-email/verify-email.component.scss b/frontend/src/app/components/auth/verify-email/verify-email.component.scss new file mode 100644 index 0000000..bbc955c --- /dev/null +++ b/frontend/src/app/components/auth/verify-email/verify-email.component.scss @@ -0,0 +1,8 @@ +@import "./../../../../assets/style/colors.scss"; +@import "./../../../../assets/style/form.scss"; +@import "./../../../../assets/style/auth-layout.scss"; + +section { + background-image: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), + url(./../../../../assets/img/backgrounds/login.png); +} diff --git a/frontend/src/app/components/auth/verify-email/verify-email.component.spec.ts b/frontend/src/app/components/auth/verify-email/verify-email.component.spec.ts new file mode 100644 index 0000000..1946524 --- /dev/null +++ b/frontend/src/app/components/auth/verify-email/verify-email.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VerifyEmailComponent } from './verify-email.component'; + +describe('VerifyEmailComponent', () => { + let component: VerifyEmailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VerifyEmailComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VerifyEmailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/auth/verify-email/verify-email.component.ts b/frontend/src/app/components/auth/verify-email/verify-email.component.ts new file mode 100644 index 0000000..460edcc --- /dev/null +++ b/frontend/src/app/components/auth/verify-email/verify-email.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { AuthService } from '../../../services/auth.service'; +import { ErrorService } from '../../../services/error.service'; +import { CommonModule } from '@angular/common'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Component({ + selector: 'app-verify-email', + standalone: true, + imports: [CommonModule, RouterLink], + templateUrl: './verify-email.component.html', + styleUrl: './verify-email.component.scss', +}) +export class VerifyEmailComponent { + verified: boolean = false; + + authData = { + mail: '', + token: '', + }; + + constructor( + private route: ActivatedRoute, + private authService: AuthService, + public errorService: ErrorService + ) {} + + ngOnInit(): void { + this.route.queryParams.subscribe((params) => { + this.authData.mail = params['email'] || ''; + this.authData.token = params['token'] || ''; + this.verifyEmail(); + }); + } + + async verifyEmail() { + const body = { + email: this.authData.mail, + token: this.authData.token, + }; + try { + await this.authService.verifyEmail(body); + this.verified = true; + this.errorService.clearError(); + } catch (error) { + this.verified = false; + this.errorMsg(error); + } + } + + errorMsg(error: any) { + if (error instanceof HttpErrorResponse) { + const errorTypes = ['error']; + for (const type of errorTypes) { + if (error.error[type]) { + this.errorService.setError(type, error.error[type]); + return; + } + } + this.errorService.clearError(); + } + } +} diff --git a/frontend/src/app/components/home/home.component.html b/frontend/src/app/components/home/home.component.html index dbd88c9..fc24b03 100644 --- a/frontend/src/app/components/home/home.component.html +++ b/frontend/src/app/components/home/home.component.html @@ -6,5 +6,6 @@ + diff --git a/frontend/src/app/components/home/home.component.ts b/frontend/src/app/components/home/home.component.ts index 2eb7ec1..1921979 100644 --- a/frontend/src/app/components/home/home.component.ts +++ b/frontend/src/app/components/home/home.component.ts @@ -8,6 +8,7 @@ import { CommonModule } from '@angular/common'; import { LoginComponent } from '../auth/login/login.component'; import { ForgotPasswordComponent } from '../auth/forgot-password/forgot-password.component'; import { BrowseComponent } from './browse/browse.component'; +import { VerifyEmailComponent } from '../auth/verify-email/verify-email.component'; @Component({ selector: 'app-home', @@ -20,6 +21,7 @@ import { BrowseComponent } from './browse/browse.component'; RegisterComponent, LoginComponent, ForgotPasswordComponent, + VerifyEmailComponent, BrowseComponent, ], templateUrl: './home.component.html', diff --git a/frontend/src/app/services/auth.service.ts b/frontend/src/app/services/auth.service.ts index 669facb..062abb3 100644 --- a/frontend/src/app/services/auth.service.ts +++ b/frontend/src/app/services/auth.service.ts @@ -11,22 +11,28 @@ export class AuthService { constructor(private http: HttpClient) {} - async login(body: any, storage: boolean): Promise { + async login(body: any, storage: boolean) { const data = await lastValueFrom( - this.http.post(`${environment.baseUrl}/auth/login/`, body) + this.http.post(`${environment.baseUrl}/auth/login/`, body) ); this.storeAuthToken(data, storage); } + async verifyEmail(body: any) { + await lastValueFrom( + this.http.post(`${environment.baseUrl}/auth/verify-email/`, body) + ); + } + storeAuthToken(data: any, storage: boolean) { storage ? localStorage.setItem('authToken', data.toString()) : sessionStorage.setItem('authToken', data.toString()); } - async register(body: any): Promise { + async register(body: any) { await lastValueFrom( - this.http.post(`${environment.baseUrl}/auth/register/`, body) + this.http.post(`${environment.baseUrl}/auth/register/`, body) ); } }