send forgot password email
This commit is contained in:
parent
876317a994
commit
052899307e
6 changed files with 149 additions and 8 deletions
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
||||||
48
backend/videoflix/templates/forgot_password_mail.html
Normal file
48
backend/videoflix/templates/forgot_password_mail.html
Normal 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>
|
||||||
|
|
@ -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()),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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') {
|
||||||
ngForm.form.reset();
|
try {
|
||||||
this.sendMailSuccess = true;
|
this.verifyEmail();
|
||||||
|
ngForm.form.reset();
|
||||||
|
} 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue