send forgot password email

This commit is contained in:
Chneemann 2024-08-06 18:18:40 +02:00
parent 876317a994
commit 052899307e
6 changed files with 149 additions and 8 deletions

View file

@ -59,7 +59,7 @@ class RegisterView(APIView):
'email': user.email, 'email': user.email,
'token': user.verify_email_token 'token': user.verify_email_token
} }
html_body = render_to_string("mail.html", merge_data) html_body = render_to_string("register_mail.html", merge_data)
message = EmailMultiAlternatives( message = EmailMultiAlternatives(
subject='Confirm your email', subject='Confirm your email',
@ -89,6 +89,52 @@ class VerifyEmailView(APIView):
else: else:
return Response({"error": "Invalid email or token."}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Invalid email or token."}, status=status.HTTP_400_BAD_REQUEST)
class ForgotPasswordView(APIView):
def post(self, request):
email = request.data.get('email')
if not email:
return Response({"error": "Email required."}, status=status.HTTP_400_BAD_REQUEST)
try:
user = CustomUser.objects.get(email=email)
user.verify_email_token = CustomUser.generate_verification_token()
user.save()
self.send_email(user)
except CustomUser.DoesNotExist:
pass
return Response(status=status.HTTP_200_OK)
def send_email(self, user):
merge_data = {
'name': user.username,
'email': user.email,
'token': user.verify_email_token
}
html_body = render_to_string("forgot_password_mail.html", merge_data)
message = EmailMultiAlternatives(
subject='Reset your Password',
body=strip_tags(html_body), # Plain text fallback
from_email='noreply@videoflix.com',
to=['andre.kempf.dev@gmail.com']
)
message.attach_alternative(html_body, "text/html")
message.send(fail_silently=False)
class ChangePasswordView(APIView):
def post(self, request):
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:
return Response(status=status.HTTP_200_OK)
else:
return Response({"error": "Invalid email or token."}, status=status.HTTP_400_BAD_REQUEST)
class AuthView(ObtainAuthToken): class AuthView(ObtainAuthToken):
authentication_classes = [authentication.TokenAuthentication] authentication_classes = [authentication.TokenAuthentication]

View file

@ -0,0 +1,48 @@
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.btn {
display: flex;
align-items: center;
padding: 9px 18px;
width: fit-content;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
border-radius: 24px;
color: white;
font-size: 16px;
font-weight: 500;
background-color: #2e3edf;
border: 1px solid #2e3edf;
transition: 300ms ease-in-out;
text-decoration: none;
cursor: pointer;
}
.btn:hover {
background-color: #fff;
color: #2e3edf;
border: 1px solid #2e3edf;
transition: 300ms ease-in-out;
}
.center {
display: flex;
justify-content: center;
}
img {
width: 249px;
height: 40px;
}
</style>
<body>
<div class="center"><img src="https://andre-kempf.com/videoflix-logo.png" alt=""></div>
<p>Dear {{ name }},</p>
<p>
We recently received a request to reset your password. If you made this request, please click on the following link to reset your password:
</p>
<a class="btn" href="http://localhost:4200/forgot-password?email={{ email }}&token={{ token }}">Reset Password</a>
<p>Please note that for security reasons, this link is only valid for 24 hours.</p>
<p>If you did not request a password reset, please ignore this email.</p>
<p>Best regards,</p>
<p>Your Videoflix team!</p>
</body>
</html>

View file

@ -17,7 +17,7 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from users import views from users import views
from auth.views import LoginView, RegisterView, VerifyEmailView, AuthView from auth.views import LoginView, RegisterView, VerifyEmailView, AuthView, ForgotPasswordView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
@ -27,5 +27,6 @@ urlpatterns = [
path('auth/login/', LoginView.as_view()), path('auth/login/', LoginView.as_view()),
path('auth/register/', RegisterView.as_view()), path('auth/register/', RegisterView.as_view()),
path('auth/verify-email/', VerifyEmailView.as_view()), path('auth/verify-email/', VerifyEmailView.as_view()),
path('auth/forgot-password/', ForgotPasswordView.as_view()),
] ]

View file

@ -3,6 +3,9 @@ import { Component } from '@angular/core';
import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component'; import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { ActivatedRoute, RouterLink } from '@angular/router'; import { ActivatedRoute, RouterLink } from '@angular/router';
import { AuthService } from '../../../services/auth.service';
import { ErrorService } from '../../../services/error.service';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-forgot-password', selector: 'app-forgot-password',
@ -14,6 +17,7 @@ import { ActivatedRoute, RouterLink } from '@angular/router';
export class ForgotPasswordComponent { export class ForgotPasswordComponent {
authData = { authData = {
mail: '', mail: '',
token: '',
password: '', password: '',
passwordConfirm: '', passwordConfirm: '',
}; };
@ -22,13 +26,21 @@ export class ForgotPasswordComponent {
queryEmail: boolean = false; queryEmail: boolean = false;
queryEmailSuccess: boolean = false; queryEmailSuccess: boolean = false;
constructor(private route: ActivatedRoute) {} constructor(
private route: ActivatedRoute,
private authService: AuthService,
public errorService: ErrorService
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe((params) => { this.route.queryParams.subscribe((params) => {
this.authData.mail = params['mail'] || ''; this.authData.mail = params['email'] || '';
this.authData.token = params['token'] || '';
this.queryEmailSuccess = params['pw-change'] || ''; this.queryEmailSuccess = params['pw-change'] || '';
}); });
if (this.authData.mail && this.authData.token) {
this.queryEmail = true;
}
} }
isUserEmailValid(emailValue: string) { isUserEmailValid(emailValue: string) {
@ -39,16 +51,44 @@ export class ForgotPasswordComponent {
onSubmit(ngForm: NgForm, mailInput: any) { onSubmit(ngForm: NgForm, mailInput: any) {
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
if (mailInput.name === 'mail') { if (mailInput.name === 'mail') {
try {
this.verifyEmail();
ngForm.form.reset(); ngForm.form.reset();
this.sendMailSuccess = true; } catch {}
} else if (mailInput.name === 'password') { } else if (mailInput.name === 'password') {
ngForm.form.reset(); ngForm.form.reset();
this.queryEmail = false; this.queryEmail = false;
this.queryEmailSuccess = true; this.queryEmailSuccess = true;
} }
console.log(this.authData);
} else { } else {
mailInput.control.markAsTouched(); mailInput.control.markAsTouched();
} }
} }
async verifyEmail() {
const body = {
email: this.authData.mail,
};
try {
await this.authService.forgotPassword(body);
this.sendMailSuccess = true;
this.errorService.clearError();
} catch (error) {
this.sendMailSuccess = 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();
}
}
} }

View file

@ -30,6 +30,12 @@ export class AuthService {
); );
} }
async forgotPassword(body: any) {
await lastValueFrom(
this.http.post(`${environment.baseUrl}/auth/forgot-password/`, body)
);
}
storeAuthToken(data: any, storage: boolean) { storeAuthToken(data: any, storage: boolean) {
storage storage
? localStorage.setItem('authToken', data.toString()) ? localStorage.setItem('authToken', data.toString())