diff --git a/config/settings.py b/config/settings.py
index 3f4df15..dda88a2 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -71,7 +71,7 @@ ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [BASE_DIR / 'templates'],
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -176,8 +176,8 @@ REST_FRAMEWORK = {
}
-#FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
-FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
+FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
+#FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
diff --git a/rog/serializers.py b/rog/serializers.py
index fddaaf9..4028602 100644
--- a/rog/serializers.py
+++ b/rog/serializers.py
@@ -155,17 +155,22 @@ class UserRegistrationSerializer(serializers.ModelSerializer):
# return user
class TempUserRegistrationSerializer(serializers.ModelSerializer):
+ password = serializers.CharField(write_only=True)
+
class Meta:
model = TempUser
- fields = ('email', 'firstname', 'lastname', 'date_of_birth', 'female')
+ fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female')
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
+ # パスワードのハッシュ化はviewで行うので、ここではそのまま保存
return TempUser.objects.create(**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)
+
class UserSerializer(serializers.ModelSerializer):
class Meta:
@@ -793,4 +798,16 @@ class EntryCreationSerializer(serializers.Serializer):
return entry
+class PasswordResetRequestSerializer(serializers.Serializer):
+ email = serializers.EmailField()
+
+class PasswordResetConfirmSerializer(serializers.Serializer):
+ new_password = serializers.CharField(write_only=True)
+ confirm_password = serializers.CharField(write_only=True)
+
+ def validate(self, data):
+ if data['new_password'] != data['confirm_password']:
+ raise serializers.ValidationError("Passwords do not match")
+ validate_password(data['new_password'])
+ return data
diff --git a/rog/templates/email/reset_password_email.txt b/rog/templates/email/reset_password_email.txt
new file mode 100644
index 0000000..af28d22
--- /dev/null
+++ b/rog/templates/email/reset_password_email.txt
@@ -0,0 +1,23 @@
+件名: 岐阜ロゲのパスワードリセットのお知らせ
+
+{{name}} 様
+
+こちらは岐阜aiネットワークのAI担当です。
+
+このメールはパスワードのリセットのご依頼によるリセット確認メールです。
+身に覚えのない方は、お手数ですが削除をお願いします。
+
+以下のリンクからパスワードのリセットが行えます。
+
+{{activation_link}}
+
+
+それでは、今後とも岐阜ロゲをよろしくお願いいたします。
+
+
+※ 本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承ください(ご質問等はinfo@gifuai.netまでお願いいたします)。もしこのメールに心当たりがない場合は破棄願います。
+
+NPO岐阜aiネットワーク 担当AI
+
+
+
diff --git a/rog/templates/password-reset-component.tsx b/rog/templates/password-reset-component.tsx
new file mode 100644
index 0000000..b793800
--- /dev/null
+++ b/rog/templates/password-reset-component.tsx
@@ -0,0 +1,116 @@
+import React, { useState } from 'react';
+import { useParams } from 'react-router-dom';
+import { Eye, EyeOff } from 'lucide-react';
+
+const PasswordReset = () => {
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [message, setMessage] = useState('');
+ const { uid, token } = useParams();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (password !== confirmPassword) {
+ setMessage('パスワードが一致しません。');
+ return;
+ }
+ try {
+ const response = await fetch(`/api/reset-password/${uid}/${token}/`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ new_password: password }),
+ });
+ const data = await response.json();
+ if (response.ok) {
+ setMessage('パスワードが正常にリセットされました。');
+ } else {
+ setMessage(data.message || 'パスワードのリセットに失敗しました。');
+ }
+ } catch (error) {
+ setMessage('エラーが発生しました。もう一度お試しください。');
+ }
+ };
+
+ return (
+
+
+
+ パスワードのリセット
+
+
+
+
+
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+
+
+ );
+};
+
+export default PasswordReset;
diff --git a/rog/templates/password_reset.html b/rog/templates/password_reset.html
new file mode 100644
index 0000000..3be87f2
--- /dev/null
+++ b/rog/templates/password_reset.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+ パスワードのリセット
+
+
+
+
+
+
+
+
diff --git a/rog/templates/password_reset_invalid.html b/rog/templates/password_reset_invalid.html
new file mode 100644
index 0000000..2c27657
--- /dev/null
+++ b/rog/templates/password_reset_invalid.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ 無効なパスワードリセットリンク
+
+
+
+
+
無効なリンク
+
このパスワードリセットリンクは無効です。新しいリセットリンクを要求してください。
+
+
+
diff --git a/rog/urls.py b/rog/urls.py
index 05cdfc2..cccc7b8 100644
--- a/rog/urls.py
+++ b/rog/urls.py
@@ -1,7 +1,7 @@
from sys import prefix
from rest_framework import urlpatterns
from rest_framework.routers import DefaultRouter
-from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView
+from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView
from django.urls import path, include
from knox import views as knox_views
@@ -90,5 +90,7 @@ urlpatterns += [
path('userdetail//',update_user_detail, name='update_user_detail'),
path('activate-member///', ActivateMemberView.as_view(), name='activate-member'),
path('activate-new-member///', ActivateNewMemberView.as_view(), name='activate-new-member'),
+ path('password-reset/', PasswordResetRequestView.as_view(), name='password_reset_request'),
+ path('reset-password///', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
]
diff --git a/rog/utils.py b/rog/utils.py
index a6719da..413361d 100644
--- a/rog/utils.py
+++ b/rog/utils.py
@@ -43,7 +43,14 @@ def send_verification_email(user, activation_link):
share_send_email(subject,body,user.email)
-
+def send_reset_password_email(email,activation_link):
+ context = {
+ 'name': email,
+ 'activation_link': activation_link,
+ }
+ logger.info(f"send_reset_password_email : {context}")
+ subject, body = load_email_template('reset_password_email.txt', context)
+ share_send_email(subject,body,email)
# 既にユーザーになっている人にチームへの参加要請メールを出す。
diff --git a/rog/views.py b/rog/views.py
index d5ebe53..ee31e0d 100644
--- a/rog/views.py
+++ b/rog/views.py
@@ -2,12 +2,17 @@ from .models import JpnSubPerf # このインポート文をファイルの先
from django.contrib.auth import get_user_model
User = get_user_model()
import traceback
+from django.contrib.auth.hashers import make_password
+
+from django.contrib.auth.tokens import default_token_generator
+from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
+from django.utils.encoding import force_bytes, force_str
import requests
from rest_framework import serializers
from django.db import IntegrityError
from django.urls import reverse
-from .utils import send_verification_email,send_invitation_email,send_team_join_email
+from .utils import send_verification_email,send_invitation_email,send_team_join_email,send_reset_password_email
from django.conf import settings
import uuid
from rest_framework.exceptions import ValidationError as DRFValidationError
@@ -26,7 +31,7 @@ from curses.ascii import NUL
from django.core.serializers import serialize
from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser,EntryMember
from rest_framework import viewsets
-from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer
+from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer, PasswordResetRequestSerializer, PasswordResetConfirmSerializer
from knox.models import AuthToken
from rest_framework import viewsets, generics, status
@@ -483,7 +488,6 @@ class LoginView(APIView):
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
@@ -1699,12 +1703,17 @@ class TempUserRegistrationView(APIView):
# 新規仮登録
serializer = TempUserRegistrationSerializer(data=request.data)
if serializer.is_valid():
- temp_user = serializer.save()
+ # シリアライザのvalidated_dataからパスワードを取得
+ password = serializer.validated_data.get('password')
+ # パスワードをハッシュ化
+ hashed_password = make_password(password)
+ # ハッシュ化されたパスワードでTempUserを作成
+ temp_user = serializer.save(password=hashed_password)
+
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})
)
@@ -1736,6 +1745,8 @@ class VerifyEmailView(APIView):
if temp_user.is_valid():
user_data = {
'email': temp_user.email,
+ 'is_rogaining':True, # ここでis_rogainingをTrueに設定
+ 'password':temp_user.password,
'is_rogaining': temp_user.is_rogaining,
'zekken_number': temp_user.zekken_number,
'event_code': temp_user.event_code,
@@ -1752,11 +1763,13 @@ class VerifyEmailView(APIView):
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'}
- )
+ user = CustomUser.objects.create(**user_data)
+
+ #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': 'エラー'})
@@ -1795,3 +1808,55 @@ class TeamMembersWithUserView(generics.ListAPIView):
team_id = self.kwargs['team_id']
return Member.objects.filter(team_id=team_id).select_related('user', 'team')
+
+class PasswordResetRequestView(APIView):
+ def post(self, request):
+ serializer = PasswordResetRequestSerializer(data=request.data)
+ if serializer.is_valid():
+ email = serializer.validated_data['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"{settings.FRONTEND_URL}/api/reset-password/{uid}/{token}/"
+ send_reset_password_email(email,reset_link)
+
+ return Response({"message": "Password reset email sent"}, status=status.HTTP_200_OK)
+ return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+class PasswordResetConfirmView(APIView):
+ def get(self, request, uidb64, token):
+ try:
+ uid = force_str(urlsafe_base64_decode(uidb64))
+ user = CustomUser.objects.get(pk=uid)
+ except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
+ user = None
+
+ if user is not None and default_token_generator.check_token(user, token):
+ return render(request, 'password_reset.html', {'uid': uidb64, 'token': token})
+ else:
+ return render(request, 'password_reset_invalid.html')
+
+ if user is not None and default_token_generator.check_token(user, token):
+ return Response({"message": "Token is valid"}, status=status.HTTP_200_OK)
+ return Response({"message": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
+
+
+ def post(self, request, uidb64, token):
+ try:
+ uid = force_str(urlsafe_base64_decode(uidb64))
+ user = CustomUser.objects.get(pk=uid)
+ except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
+ return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
+
+ if default_token_generator.check_token(user, token):
+ serializer = PasswordResetConfirmSerializer(data=request.data)
+ if serializer.is_valid():
+ user.set_password(serializer.validated_data['new_password'])
+ user.save()
+ return Response({"message": "Password has been reset successfully"}, status=status.HTTP_200_OK)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
+
+