user is now unlocked by his email and token from the email link
This commit is contained in:
parent
3b4e1634ab
commit
133735e2f7
12 changed files with 156 additions and 14 deletions
|
|
@ -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
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
Thank you for registering with Videoflix. To complete your registration
|
||||
and verify your email address, please click the link below:
|
||||
</p>
|
||||
<a class="btn" href="http://localhost:4200/verify-email?token={{ token }}&email={{ email }}">Activate account</a>
|
||||
<a class="btn" href="http://localhost:4200/verify-email?email={{ email }}&token={{ token }}">Activate account</a>
|
||||
<p>
|
||||
If you did not create an account with us, please disregard this email.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class CustomUserAdmin(UserAdmin):
|
|||
'Individual data',
|
||||
{
|
||||
'fields': (
|
||||
'verify_email',
|
||||
'verify_email_token',
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<section>
|
||||
<div class="content">
|
||||
<div class="headline">Verify Email</div>
|
||||
<div class="note">
|
||||
@if (verified) {
|
||||
<p>Your email has been successfully verified! You can now log in.</p>
|
||||
<a routerLink="/login">To the login</a>
|
||||
} @else {
|
||||
<p>
|
||||
There was an issue verifying your email.<br />Please try again or
|
||||
contact support for assistance.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="errorService.error$ | async as errors">
|
||||
<div *ngIf="errors?.['error']" class="error-msg">
|
||||
<p>{{ errors["error"] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<VerifyEmailComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VerifyEmailComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VerifyEmailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,6 @@
|
|||
<app-forgot-password
|
||||
*ngIf="currentRoute === 'forgot-password'"
|
||||
></app-forgot-password>
|
||||
<app-verify-email *ngIf="currentRoute === 'verify-email'"></app-verify-email>
|
||||
<app-footer></app-footer>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -11,22 +11,28 @@ export class AuthService {
|
|||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
async login(body: any, storage: boolean): Promise<void> {
|
||||
async login(body: any, storage: boolean) {
|
||||
const data = await lastValueFrom(
|
||||
this.http.post<any>(`${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<void> {
|
||||
async register(body: any) {
|
||||
await lastValueFrom(
|
||||
this.http.post<any>(`${environment.baseUrl}/auth/register/`, body)
|
||||
this.http.post(`${environment.baseUrl}/auth/register/`, body)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue