diff --git a/config/settings.py b/config/settings.py index 7a89a6c..3f4df15 100644 --- a/config/settings.py +++ b/config/settings.py @@ -244,3 +244,11 @@ LOGGING = { }, }, } + +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', +] + diff --git a/rog/backend.py b/rog/backend.py index 9ad342b..b70c44d 100644 --- a/rog/backend.py +++ b/rog/backend.py @@ -3,12 +3,18 @@ from django.conf import settings from .models import CustomUser from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model +from django.contrib.auth.hashers import check_password +import logging + +logger = logging.getLogger(__name__) + class EmailOrUsernameModelBackend(ModelBackend): """ This is a ModelBacked that allows authentication with either a username or an email address. + """ """ def authenticate(self, username=None, password=None): if '@' in username: @@ -26,4 +32,35 @@ class EmailOrUsernameModelBackend(ModelBackend): try: return CustomUser.objects.get(pk=username) except get_user_model().DoesNotExist: - return None \ No newline at end of file + return None + """ + + def authenticate(self, request, username=None, password=None, **kwargs): + if '@' in username: + kwargs = {'email': username} + else: + kwargs = {'username': username} + try: + user = CustomUser.objects.get(**kwargs) + if check_password(password, user.password): + logger.info(f"User authenticated successfully: {username}") + return user + else: + logger.warning(f"Password mismatch for user: {username}") + except CustomUser.DoesNotExist: + logger.warning(f"User does not exist: {username}") + except Exception as e: + logger.error(f"Authentication error for {username}: {str(e)}") + return None + + def get_user(self, user_id): + try: + user = CustomUser.objects.get(pk=user_id) + logger.info(f"User retrieved: {user.username or user.email}") + return user + except CustomUser.DoesNotExist: + logger.warning(f"User with id {user_id} does not exist") + return None + except Exception as e: + logger.error(f"Error retrieving user with id {user_id}: {str(e)}") + return None diff --git a/rog/models.py b/rog/models.py index 30ba2e3..f1c4ce5 100644 --- a/rog/models.py +++ b/rog/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth.hashers import make_password from dataclasses import field import email from enum import unique @@ -146,28 +147,28 @@ class JpnAdminMainPerf(models.Model): # ### # ### Cities # ### -# class JpnSubPerf(models.Model): -# geom = models.MultiPolygonField(blank=True, null=True) -# adm0_en = models.CharField(max_length=254, blank=True, null=True) -# adm0_ja = models.CharField(max_length=254, blank=True, null=True) -# adm0_pcode = models.CharField(max_length=254, blank=True, null=True) -# adm1_en = models.CharField(max_length=254, blank=True, null=True) -# adm1_ja = models.CharField(max_length=254, blank=True, null=True) -# adm1_pcode = models.CharField(max_length=254, blank=True, null=True) -# adm2_ja = models.CharField(max_length=254, blank=True, null=True) -# adm2_en = models.CharField(max_length=254, blank=True, null=True) -# adm2_pcode = models.CharField(max_length=254, blank=True, null=True) -# name_modified = models.CharField(max_length=254, blank=True, null=True) -# area_name = models.CharField(max_length=254, blank=True, null=True) -# list_order =models.IntegerField(default=0) +class JpnSubPerf(models.Model): + geom = models.MultiPolygonField(blank=True, null=True) + adm0_en = models.CharField(max_length=254, blank=True, null=True) + adm0_ja = models.CharField(max_length=254, blank=True, null=True) + adm0_pcode = models.CharField(max_length=254, blank=True, null=True) + adm1_en = models.CharField(max_length=254, blank=True, null=True) + adm1_ja = models.CharField(max_length=254, blank=True, null=True) + adm1_pcode = models.CharField(max_length=254, blank=True, null=True) + adm2_ja = models.CharField(max_length=254, blank=True, null=True) + adm2_en = models.CharField(max_length=254, blank=True, null=True) + adm2_pcode = models.CharField(max_length=254, blank=True, null=True) + name_modified = models.CharField(max_length=254, blank=True, null=True) + area_name = models.CharField(max_length=254, blank=True, null=True) + list_order =models.IntegerField(default=0) -# class Meta: -# managed = False -# db_table = 'jpn_sub_perf' -# indexes = [ -# models.Index(fields=['geom'], name='jpn_sub_perf_geom_idx'), -# # Add other fields for indexing as per the requirements -# ] + class Meta: + managed = False + db_table = 'jpn_sub_perf' + indexes = [ + models.Index(fields=['geom'], name='jpn_sub_perf_geom_idx'), + # Add other fields for indexing as per the requirements + ] ### ### Gifu Areas @@ -234,6 +235,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): objects = CustomUserManager() + def set_password(self, raw_password): + self.password = make_password(raw_password) + self._password = raw_password + def __str__(self): return self.email @@ -253,6 +258,20 @@ class TempUser(models.Model): created_at = models.DateTimeField(auto_now_add=True) expires_at = models.DateTimeField() + def set_password(self, raw_password): + self.password = make_password(raw_password) + + def check_password(self, raw_password): + return check_password(raw_password, self.password) + + # TempUserの作成時にこのメソッドを使用 + @classmethod + def create_temp_user(cls, email, password, **kwargs): + temp_user = cls(email=email, **kwargs) + temp_user.set_password(password) + temp_user.save() + return temp_user + def __str__(self): return self.email diff --git a/rog/serializers.py b/rog/serializers.py index b81bfcf..fddaaf9 100644 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -1,8 +1,14 @@ +from django.contrib.auth.hashers import make_password, check_password + +from django.contrib.auth import get_user_model +User = get_user_model() + import uuid from django.db import IntegrityError from django.conf import settings from django.urls import reverse from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError from django.db import transaction from rest_framework import serializers @@ -73,10 +79,11 @@ class GifuAreaSerializer(serializers.ModelSerializer): class UserRegistrationSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True, validators=[validate_password]) + password2 = serializers.CharField(write_only=True, required=True, validators=[validate_password]) class Meta: model = CustomUser - fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female') + fields = ('email', 'password', 'password2', 'firstname', 'lastname', 'date_of_birth', 'female') extra_kwargs = { 'email': {'required': True}, 'firstname': {'required': True}, @@ -84,38 +91,68 @@ class UserRegistrationSerializer(serializers.ModelSerializer): 'date_of_birth': {'required': True}, } + def validate(self, attrs): + if attrs['password'] != attrs['password2']: + raise serializers.ValidationError({"password": "Password fields didn't match."}) + + try: + validate_password(attrs['password']) + except ValidationError as e: + raise serializers.ValidationError({"password": list(e.messages)}) + + return attrs + def validate_email(self, value): if CustomUser.objects.filter(email=value).exists() or TempUser.objects.filter(email=value).exists(): raise serializers.ValidationError("この電子メールアドレスは既に使用されています。") return value def create(self, validated_data): - try: - user = CustomUser.objects.create_user( - email=validated_data['email'], - password=validated_data['password'], - firstname=validated_data['firstname'], - lastname=validated_data['lastname'], - date_of_birth=validated_data['date_of_birth'], - female=validated_data.get('female', False), - group='' # この値は必要に応じて変更してください - ) - return user - except ValidationError as e: - raise serializers.ValidationError({"password": list(e.messages)}) + raw_password = validated_data.get('password') + + # デバッグコード + hashed_password = make_password(raw_password) + print(f"Hashed password during registration: {hashed_password}") + is_valid = check_password(raw_password, hashed_password) + print(f"Password is valid during registration: {is_valid}") + + validated_data['password'] = hashed_password + return super(UserRegistrationSerializer, self).create(validated_data) + + #validated_data['password'] = make_password(validated_data.get('password')) + #return super(UserRegistrationSerializer, self).create(validated_data) + +# try: +# with transaction.atomic(): +# password = validated_data['password'].encode('utf-8').decode('utf-8') +# +# user = CustomUser.objects.create_user( +# email=validated_data['email'], +# password=password, # validated_data['password'], +# firstname=validated_data['firstname'], +# lastname=validated_data['lastname'], +# date_of_birth=validated_data['date_of_birth'], +# female=validated_data.get('female', False), +# group='' # この値は必要に応じて変更してください +# ) +# logger.debug(f"Creating user with data: {validated_data}") +# user.set_password(validated_data['password']) +# user.save() +# +# return user +# except ValidationError as e: +# raise serializers.ValidationError({"password": list(e.messages)}) -''' -class CreateUserSerializer(serializers.ModelSerializer): - class Meta: - model = CustomUser - fields = ('email', 'password') - extra_kwargs = {'password': {'write_only': True}} - - def create(self, validated_data): - user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','') - return user -''' +#class CreateUserSerializer(serializers.ModelSerializer): +# class Meta: +# model = CustomUser +# fields = ('email', 'password') +# extra_kwargs = {'password': {'write_only': True}} +# +# def create(self, validated_data): +# user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','') +# return user class TempUserRegistrationSerializer(serializers.ModelSerializer): class Meta: @@ -124,6 +161,9 @@ class TempUserRegistrationSerializer(serializers.ModelSerializer): def create(self, validated_data): validated_data['verification_code'] = str(uuid.uuid4()) + raw_password = validated_data.get('password') + hashed_password = make_password(raw_password) + validated_data['password'] = hashed_password return TempUser.objects.create(**validated_data) @@ -162,14 +202,32 @@ class RogUserSerializer(serializers.ModelSerializer): class LoginUserSerializer(serializers.Serializer): - email = serializers.CharField() + #email = serializers.CharField() + email = serializers.EmailField() password = serializers.CharField() def validate(self, data): - user = authenticate(**data) - if user and user.is_active: - return user - raise serializers.ValidationError("Invalid Details.") + email = data.get('email') + password = data.get('password') + + if email and password: + user = authenticate(username=email, password=password) + if user: + if user.is_active: + return user + raise serializers.ValidationError("User account is disabled.") + else: + # Check if the user exists + try: + user_obj = User.objects.get(email=email) + raise serializers.ValidationError("Incorrect password.") + except User.DoesNotExist: + raise serializers.ValidationError("User with this email does not exist.") + else: + raise serializers.ValidationError("Must include 'email' and 'password'.") + + + class UseractionsSerializer(serializers.ModelSerializer): diff --git a/rog/views.py b/rog/views.py index 3524ab8..d5ebe53 100644 --- a/rog/views.py +++ b/rog/views.py @@ -1,3 +1,8 @@ +from .models import JpnSubPerf # このインポート文をファイルの先頭に追加 +from django.contrib.auth import get_user_model +User = get_user_model() +import traceback + import requests from rest_framework import serializers from django.db import IntegrityError @@ -398,13 +403,28 @@ def ExtentForLocations(request): return JsonResponse(locs['geom__extent'], safe=False) -def ExtentForSubPerf(request): +def ExtentForSubPerf_old(request): sub_perf_id = request.GET.get('sub_perf') sub_perf = JpnSubPerf.objects.get(id=sub_perf_id) ext = sub_perf.geom.extent # iata = serializers.serialize("json",ext) return JsonResponse(ext, safe=False) + +def ExtentForSubPerf(request): + sub_perf_id = request.GET.get('sub_perf') + if not sub_perf_id: + return JsonResponse({"error": "sub_perf parameter is required"}, status=400) + try: + sub_perf = JpnSubPerf.objects.get(id=sub_perf_id) + ext = sub_perf.geom.extent + return JsonResponse(ext, safe=False) + except ObjectDoesNotExist: + return JsonResponse({"error": "Specified sub_perf does not exist"}, status=404) + except Exception as e: + return JsonResponse({"error": "Error on ExtentForSubPerf : {e}"}, status=404) + + def CatView(request): lat1 = float(request.GET.get('la1')) @@ -457,17 +477,94 @@ class RegistrationAPI(generics.GenericAPIView): }) +class LoginView(APIView): + def post(self, request): + email = request.data.get('email') + password = request.data.get('password') + + # デバッグコード + from django.contrib.auth.hashers import make_password, check_password + user = CustomUser.objects.filter(email=email).first() + if user: + stored_hash = user.password + print(f"Stored hashed password: {stored_hash}") + is_valid = check_password(raw_password, stored_hash) + print(f"Password is valid during login: {is_valid}") + + user = authenticate(request, username=email, password=raw_password) + if user: + token, _ = Token.objects.get_or_create(user=user) + return Response({'token': token.key}, status=status.HTTP_200_OK) + else: + return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST) + + + + #user = authenticate(request, username=email, password=password) + #if user: + # token, _ = Token.objects.get_or_create(user=user) + # return Response({'token': token.key}, status=status.HTTP_200_OK) + #else: + # return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST) + +class PasswordResetView(APIView): + def post(self, request): + email = request.data.get('email') + user = CustomUser.objects.filter(email=email).first() + if user: + token = default_token_generator.make_token(user) + uid = urlsafe_base64_encode(force_bytes(user.pk)) + reset_link = f"https://yourwebsite.com/reset-password/{uid}/{token}/" + send_mail( + 'Password Reset', + f'Click here to reset your password: {reset_link}', + 'noreply@yourwebsite.com', + [email], + fail_silently=False, + ) + return Response({'message': 'Password reset email sent'}, status=status.HTTP_200_OK) + else: + return Response({'error': 'User not found'}, status=status.HTTP_400_BAD_REQUEST) + + + + class LoginAPI(generics.GenericAPIView): + serializer_class = LoginUserSerializer def post(self, request, *args, **kwargs): + logger.info(f"Login attempt for user: {request.data.get('email', 'email not provided')}") + logger.debug(f"Request data: {request.data}") + serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - user = serializer.validated_data - return Response({ - "user": UserSerializer(user, context=self.get_serializer_context()).data, - "token": AuthToken.objects.create(user)[1] - }) + try: + serializer.is_valid(raise_exception=True) + user = serializer.validated_data + logger.info(f"User {user.email} logged in successfully") + + return Response({ + "user": UserSerializer(user, context=self.get_serializer_context()).data, + "token": AuthToken.objects.create(user)[1] + }) + except serializers.ValidationError as e: + logger.error(f"Login failed for user {request.data.get('email', 'email not provided')}: {str(e)}") + logger.error(f"Serializer errors: {serializer.errors}") + + error_msg = serializer.errors.get('non_field_errors', ['ログインに失敗しました。'])[0] + return Response({ + "error": error_msg, + "details": serializer.errors + }, status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + logger.error(f"Unexpected error during login for user {request.data.get('email', 'email not provided')}: {str(e)}") + logger.error(f"Traceback: {traceback.format_exc()}") + + return Response({ + "error": "予期せぬエラーが発生しました。", + "details": str(e) + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + class UserUpdateAPI(generics.UpdateAPIView): permission_classes = [permissions.IsAuthenticated] @@ -807,7 +904,20 @@ class RegistrationView(APIView): try: user = serializer.save() logger.info(f"New user registered: {user.email}") + + # パスワードを取得 + password = serializer.validated_data.pop('password') + # ユーザーを作成するが、まだ保存しない + user = serializer.save(commit=False) + # パスワードを明示的に設定 + user.set_password(password) + + # ユーザーを保存 + user.save() + logger.info(f"New user registered: {user.email}") return Response({"message": "ユーザー登録が完了しました。"}, status=status.HTTP_201_CREATED) + + except Exception as e: logger.error(f"Error during user registration: {str(e)}") return Response({"error": "ユーザー登録中にエラーが発生しました。"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -1592,6 +1702,8 @@ class TempUserRegistrationView(APIView): temp_user = serializer.save() verification_code = uuid.uuid4() temp_user.verification_code = verification_code + #password = serializer.validated_data.pop('password') + #temp_user.set_password(password) temp_user.save() verification_url = request.build_absolute_uri( reverse('verify-email', kwargs={'verification_code': verification_code}) @@ -1634,12 +1746,20 @@ class VerifyEmailView(APIView): 'date_of_birth': temp_user.date_of_birth, 'female': temp_user.female, } - # CustomUserを作成 - user = CustomUser.objects.create_user( - email=user_data['email'], - password=temp_user.password, - **{k: v for k, v in user_data.items() if k != 'email'} - ) + + # パスワードを安全にハッシュ化 + #hashed_password = make_password(temp_user.password) + + try: + # CustomUserを作成 + user = CustomUser.objects.create_user( + email=user_data['email'], + password=temp_user.password, + **{k: v for k, v in user_data.items() if k != 'email'} + ) + except ValidationError as e: + # パスワードのバリデーションエラーなどの処理 + return render(request, 'verification_error.html', {'message': str(e), 'title': 'エラー'}) # チームへの追加処理(もし必要なら) if hasattr(temp_user, 'team_id'):