|
12 | 12 | from .serializers import ( |
13 | 13 | LaunchpadJobTaskSerializer, LaunchpadLeaderBoardSerializer, LaunchpadParticipantsSerializer, LaunchpadUserListSerializer, |
14 | 14 | CollegeDataSerializer, LaunchpadUserSerializer, UserProfileUpdateSerializer,LaunchpadJobUpdateSerializer, |
15 | | - LaunchpadUpdateUserSerializer, LaunchPadRankSerializer, TaskCompletedLeaderBoardSerializer, TaskVerificationSerializer, LaunchpadCompanyPublicSerializer |
| 15 | + LaunchpadUpdateUserSerializer, LaunchPadRankSerializer, TaskCompletedLeaderBoardSerializer, TaskVerificationSerializer, LaunchpadCompanyPublicSerializer,ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer |
16 | 16 | ) |
17 | 17 | from api.dashboard.profile.profile_serializer import ( |
18 | 18 | UserProfileSerializer, LinkSocials, UserLevelSerializer, UserLogSerializer, |
|
39 | 39 | from django.core.mail import send_mail, EmailMessage |
40 | 40 | from django.template.loader import render_to_string |
41 | 41 | import decouple |
| 42 | +import secrets |
| 43 | + |
42 | 44 |
|
43 | 45 | def send_template_mail(context: dict, subject: str, address: List[str], attachment: str = None): |
44 | 46 | """ |
@@ -717,6 +719,11 @@ def post(self, request): |
717 | 719 | if not check_password(password, recruiter.password): |
718 | 720 | return CustomResponse(general_message="Invalid credentials.").get_failure_response() |
719 | 721 |
|
| 722 | + if not recruiter.company.is_verified: |
| 723 | + return CustomResponse( |
| 724 | + general_message="Account does not exist" |
| 725 | + ).get_failure_response() |
| 726 | + |
720 | 727 | access_token, refresh_token = generate_launchpad_jwt(recruiter, "recruiter") |
721 | 728 |
|
722 | 729 | return CustomResponse(response={ |
@@ -2875,3 +2882,176 @@ def get(self, request): |
2875 | 2882 | return CustomResponse().paginated_response( |
2876 | 2883 | data=data, pagination=paginated_queryset.get("pagination") |
2877 | 2884 | ) |
| 2885 | + |
| 2886 | +class ForgotPasswordAPI(APIView): |
| 2887 | + def post(self, request): |
| 2888 | + serializer = ForgotPasswordSerializer(data=request.data) |
| 2889 | + |
| 2890 | + if not serializer.is_valid(): |
| 2891 | + return CustomResponse( |
| 2892 | + message=serializer.errors, |
| 2893 | + general_message="Invalid request" |
| 2894 | + ).get_failure_response() |
| 2895 | + |
| 2896 | + email = serializer.validated_data['email'] |
| 2897 | + user_type = serializer.validated_data['user_type'] |
| 2898 | + |
| 2899 | + # Generate reset token |
| 2900 | + reset_token = secrets.token_urlsafe(32) |
| 2901 | + expires_at = timezone.now() + timedelta(hours=1) # Token expires in 1 hour |
| 2902 | + |
| 2903 | + try: |
| 2904 | + if user_type == 'company': |
| 2905 | + user = LaunchpadCompanies.objects.get(poc_email=email) |
| 2906 | + user.reset_token = reset_token |
| 2907 | + user.reset_token_expires = expires_at |
| 2908 | + user.save() |
| 2909 | + |
| 2910 | + user_name = user.name or user.poc_name |
| 2911 | + user_email = user.poc_email |
| 2912 | + |
| 2913 | + else: # recruiter |
| 2914 | + user = LaunchpadRecruiters.objects.get(email=email) |
| 2915 | + user.reset_token = reset_token |
| 2916 | + user.reset_token_expires = expires_at |
| 2917 | + user.save() |
| 2918 | + |
| 2919 | + user_name = user.name |
| 2920 | + user_email = user.email |
| 2921 | + |
| 2922 | + # Send reset email |
| 2923 | + try: |
| 2924 | + reset_link = f"{decouple.config('FR_DOMAIN_NAME')}/reset-password?token={reset_token}&type={user_type}" |
| 2925 | + send_template_mail( |
| 2926 | + context={ |
| 2927 | + "email": user_email, |
| 2928 | + "full_name": user_name, |
| 2929 | + "reset_link": reset_link, |
| 2930 | + "expires_in": "1 hour" |
| 2931 | + }, |
| 2932 | + subject="Password Reset - MuLearn Launchpad", |
| 2933 | + address=["launchpad-password-reset.html"] |
| 2934 | + ) |
| 2935 | + except Exception as e: |
| 2936 | + return CustomResponse( |
| 2937 | + general_message="Failed to send reset email. Please try again." |
| 2938 | + ).get_failure_response() |
| 2939 | + |
| 2940 | + return CustomResponse( |
| 2941 | + general_message="Password reset link has been sent to your email." |
| 2942 | + ).get_success_response() |
| 2943 | + |
| 2944 | + except (LaunchpadCompanies.DoesNotExist, LaunchpadRecruiters.DoesNotExist): |
| 2945 | + # Don't reveal if user exists or not for security |
| 2946 | + return CustomResponse( |
| 2947 | + general_message="If an account with this email exists, a password reset link has been sent." |
| 2948 | + ).get_success_response() |
| 2949 | + |
| 2950 | +class ResetPasswordAPI(APIView): |
| 2951 | + def post(self, request): |
| 2952 | + serializer = ResetPasswordSerializer(data=request.data) |
| 2953 | + |
| 2954 | + if not serializer.is_valid(): |
| 2955 | + return CustomResponse( |
| 2956 | + message=serializer.errors, |
| 2957 | + general_message="Invalid request" |
| 2958 | + ).get_failure_response() |
| 2959 | + |
| 2960 | + user = serializer.validated_data['user'] |
| 2961 | + new_password = serializer.validated_data['new_password'] |
| 2962 | + |
| 2963 | + # Reset password |
| 2964 | + user.password = make_password(new_password) |
| 2965 | + user.reset_token = None |
| 2966 | + user.reset_token_expires = None |
| 2967 | + user.save() |
| 2968 | + |
| 2969 | + return CustomResponse( |
| 2970 | + general_message="Password has been reset successfully. You can now login with your new password." |
| 2971 | + ).get_success_response() |
| 2972 | + |
| 2973 | +class VerifyResetTokenAPI(APIView): |
| 2974 | + def post(self, request): |
| 2975 | + token = request.data.get('token') |
| 2976 | + user_type = request.data.get('user_type') |
| 2977 | + |
| 2978 | + if not token or not user_type: |
| 2979 | + return CustomResponse( |
| 2980 | + general_message="Token and user type are required." |
| 2981 | + ).get_failure_response() |
| 2982 | + |
| 2983 | + now = timezone.now() |
| 2984 | + |
| 2985 | + try: |
| 2986 | + if user_type == 'company': |
| 2987 | + user = LaunchpadCompanies.objects.get( |
| 2988 | + reset_token=token, |
| 2989 | + reset_token_expires__gt=now |
| 2990 | + ) |
| 2991 | + user_name = user.name or user.poc_name |
| 2992 | + else: |
| 2993 | + user = LaunchpadRecruiters.objects.get( |
| 2994 | + reset_token=token, |
| 2995 | + reset_token_expires__gt=now |
| 2996 | + ) |
| 2997 | + user_name = user.name |
| 2998 | + |
| 2999 | + return CustomResponse( |
| 3000 | + response={ |
| 3001 | + 'valid': True, |
| 3002 | + 'user_name': user_name, |
| 3003 | + 'expires_at': user.reset_token_expires |
| 3004 | + }, |
| 3005 | + general_message="Token is valid" |
| 3006 | + ).get_success_response() |
| 3007 | + |
| 3008 | + except (LaunchpadCompanies.DoesNotExist, LaunchpadRecruiters.DoesNotExist): |
| 3009 | + return CustomResponse( |
| 3010 | + response={'valid': False}, |
| 3011 | + general_message="Invalid or expired token" |
| 3012 | + ).get_failure_response() |
| 3013 | + |
| 3014 | +class ChangePasswordAPI(APIView): |
| 3015 | + authentication_classes = [LaunchpadJWTPermission] |
| 3016 | + |
| 3017 | + def post(self, request): |
| 3018 | + user_type = request.auth["user_type"] |
| 3019 | + user_id = request.auth["id"] |
| 3020 | + |
| 3021 | + serializer = ChangePasswordSerializer(data=request.data) |
| 3022 | + |
| 3023 | + if not serializer.is_valid(): |
| 3024 | + return CustomResponse( |
| 3025 | + message=serializer.errors, |
| 3026 | + general_message="Invalid request" |
| 3027 | + ).get_failure_response() |
| 3028 | + |
| 3029 | + current_password = serializer.validated_data['current_password'] |
| 3030 | + new_password = serializer.validated_data['new_password'] |
| 3031 | + |
| 3032 | + try: |
| 3033 | + if user_type == 'company': |
| 3034 | + user = LaunchpadCompanies.objects.get(id=user_id) |
| 3035 | + else: |
| 3036 | + user = LaunchpadRecruiters.objects.get(id=user_id) |
| 3037 | + |
| 3038 | + # Verify current password |
| 3039 | + if not check_password(current_password, user.password): |
| 3040 | + return CustomResponse( |
| 3041 | + message={'current_password': ['Current password is incorrect.']}, |
| 3042 | + general_message="Password change failed" |
| 3043 | + ).get_failure_response() |
| 3044 | + |
| 3045 | + # Update password |
| 3046 | + user.password = make_password(new_password) |
| 3047 | + user.save() |
| 3048 | + |
| 3049 | + return CustomResponse( |
| 3050 | + general_message="Password changed successfully." |
| 3051 | + ).get_success_response() |
| 3052 | + |
| 3053 | + except (LaunchpadCompanies.DoesNotExist, LaunchpadRecruiters.DoesNotExist): |
| 3054 | + return CustomResponse( |
| 3055 | + general_message="User not found." |
| 3056 | + ).get_failure_response() |
| 3057 | + |
0 commit comments