mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Update password function
This commit is contained in:
parent
47564c9d91
commit
1aa051e8ff
|
@ -70,6 +70,17 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
'last_name',
|
||||
]
|
||||
|
||||
class ChangePasswordSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for password change endpoint.
|
||||
"""
|
||||
old_password = serializers.CharField(required=True)
|
||||
new_password = serializers.CharField(required=True)
|
||||
|
||||
# def validate(self, attrs):
|
||||
# if attrs['new_password'] != "123":
|
||||
# raise serializers.ValidationError({"password": r"Пароль не '123'"})
|
||||
# return attrs
|
||||
|
||||
class SignupSerializer(serializers.ModelSerializer):
|
||||
''' User profile create '''
|
||||
|
|
|
@ -10,4 +10,5 @@ urlpatterns = [
|
|||
path('api/signup', views.SignupAPIView.as_view()),
|
||||
path('api/login', views.LoginAPIView.as_view()),
|
||||
path('api/logout', views.LogoutAPIView.as_view()),
|
||||
path('api/change-password', views.UpdatePassword.as_view()),
|
||||
]
|
||||
|
|
|
@ -6,6 +6,7 @@ from rest_framework.response import Response
|
|||
from . import serializers
|
||||
from . import models
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class LoginAPIView(views.APIView):
|
||||
'''
|
||||
|
@ -74,3 +75,32 @@ class UserProfileAPIView(generics.RetrieveUpdateAPIView):
|
|||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
class UpdatePassword(views.APIView):
|
||||
"""
|
||||
An endpoint for changing password.
|
||||
"""
|
||||
# {"username": "admin", "password": "1234"}
|
||||
# {"old_password": "1234", "new_password": "1234"}
|
||||
permission_classes = (permissions.IsAuthenticated, )
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
|
||||
serializer = serializers.ChangePasswordSerializer(data=request.data)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Check old password
|
||||
old_password = serializer.data.get("old_password")
|
||||
if not self.object.check_password(old_password):
|
||||
return Response({"old_password": ["Wrong password."]},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
# set_password also hashes the password that the user will get
|
||||
self.object.set_password(serializer.data.get("new_password"))
|
||||
self.object.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Route, Routes } from 'react-router-dom';
|
|||
import Footer from './components/Footer';
|
||||
import Navigation from './components/Navigation/Navigation';
|
||||
import ToasterThemed from './components/ToasterThemed';
|
||||
import { useConceptTheme } from './context/ThemeContext';
|
||||
import CreateRSFormPage from './pages/CreateRSFormPage';
|
||||
import HomePage from './pages/HomePage';
|
||||
import LibraryPage from './pages/LibraryPage';
|
||||
|
@ -15,6 +16,9 @@ import RSFormPage from './pages/RSFormPage';
|
|||
import UserProfilePage from './pages/UserProfilePage';
|
||||
|
||||
function App () {
|
||||
const {noNavigation} = useConceptTheme()
|
||||
const nav_height: string = noNavigation ? "7.5rem" : "7.4rem";
|
||||
const main_clsN: string = `min-h-[calc(100vh-${nav_height})] px-2 h-fit`;
|
||||
return (
|
||||
<div className='antialiased clr-app'>
|
||||
<Navigation />
|
||||
|
@ -24,7 +28,7 @@ function App () {
|
|||
draggable={false}
|
||||
pauseOnFocusLoss={false}
|
||||
/>
|
||||
<main className='min-h-[calc(100vh-7.5rem)] px-2 h-fit'>
|
||||
<main className={main_clsN}>
|
||||
<Routes>
|
||||
<Route path='/' element={ <HomePage/>} />
|
||||
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { ErrorInfo } from '../components/BackendError';
|
||||
import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI';
|
||||
import { IUserProfile, IUserUpdateData } from '../utils/models';
|
||||
import { DataCallback, getProfile, patchPassword,patchProfile } from '../utils/backendAPI';
|
||||
import { IUserProfile, IUserUpdateData, IUserUpdatePassword } from '../utils/models';
|
||||
import { useAuth } from './AuthContext';
|
||||
|
||||
interface IUserProfileContextContext {
|
||||
interface IUserProfileContext {
|
||||
user: IUserProfile | undefined
|
||||
loading: boolean
|
||||
processing: boolean
|
||||
error: ErrorInfo
|
||||
setError: (error: ErrorInfo) => void
|
||||
updateUser: (data: IUserUpdateData, callback?: DataCallback<IUserProfile>) => void
|
||||
updatePassword: (data: IUserUpdatePassword, callback?: () => void) => void
|
||||
}
|
||||
|
||||
const ProfileContext = createContext<IUserProfileContextContext | null>(null);
|
||||
const ProfileContext = createContext<IUserProfileContext | null>(null);
|
||||
|
||||
export const useUserProfile = () => {
|
||||
const context = useContext(ProfileContext);
|
||||
if (!context) {
|
||||
|
@ -33,6 +36,7 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
const auth = useAuth()
|
||||
|
||||
const reload = useCallback(
|
||||
() => {
|
||||
|
@ -63,13 +67,29 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
|||
}, [setUser]
|
||||
);
|
||||
|
||||
const updatePassword = useCallback(
|
||||
(data: IUserUpdatePassword, callback?: () => void) => {
|
||||
setError(undefined);
|
||||
patchPassword({
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => { setError(error); },
|
||||
onSuccess: () => {
|
||||
setUser(undefined);
|
||||
auth.logout();
|
||||
if (callback) callback();
|
||||
}});
|
||||
}, [setUser, auth]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<ProfileContext.Provider
|
||||
value={{user, updateUser, error, loading, setError, processing}}
|
||||
value={{user, updateUser, updatePassword, error, loading, setError, processing}}
|
||||
>
|
||||
{children}
|
||||
</ProfileContext.Provider>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import { useUserProfile } from '../../context/UserProfileContext';
|
||||
import { IUserUpdatePassword } from '../../utils/models';
|
||||
|
||||
|
||||
export function ChangePassword() {
|
||||
const { updatePassword, processing } = useUserProfile();
|
||||
|
||||
const [old_password, setOldPassword] = useState('');
|
||||
const [new_password, setNewPassword] = useState('');
|
||||
const [new_password_repeat, setNewPasswordRepeat] = useState('');
|
||||
const [password_equal, setPasswordEqual] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const input_class: string = `flex-grow max-w-xl px-3 py-2 border center min-w-full ${password_equal ? "" : "text-red-500"}`
|
||||
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (new_password !== new_password_repeat) {
|
||||
setPasswordEqual(false);
|
||||
toast.error('Пароли не совпадают');
|
||||
}
|
||||
else {
|
||||
const data: IUserUpdatePassword = {
|
||||
old_password: old_password,
|
||||
new_password: new_password,
|
||||
};
|
||||
updatePassword(data, () => {toast.success('Изменения сохранены'); navigate('/login')});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex-grow max-w-sm px-4'>
|
||||
<form onSubmit={handleSubmit} className='flex-grow min-h-full px-6 py-2 border min-w-fit bg-slate-200'>
|
||||
<TextInput id='old_password'
|
||||
type='password'
|
||||
label='Введите старый пароль:'
|
||||
value={old_password}
|
||||
onChange={event => (setOldPassword(event.target.value))}
|
||||
/>
|
||||
<TextInput id='new_password'
|
||||
className={input_class}
|
||||
label="Введите новый пароль:"
|
||||
value={new_password}
|
||||
onChange={event => (setNewPassword(event.target.value), setPasswordEqual(true))}
|
||||
/>
|
||||
<TextInput id='new_password'
|
||||
className={input_class}
|
||||
label="Повторите новый пароль:"
|
||||
value={new_password_repeat}
|
||||
onChange={event => (setNewPasswordRepeat(event.target.value), setPasswordEqual(true))}
|
||||
/>
|
||||
<div className='relative flex justify-center my-4 border'>
|
||||
<button
|
||||
type='submit'
|
||||
className='absolute bottom-0 px-2 py-1 bg-blue-500 border'
|
||||
disabled={processing}>
|
||||
<span>Сменить пароль</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
)}
|
|
@ -4,15 +4,20 @@ import { toast } from 'react-toastify';
|
|||
import TextInput from '../../components/Common/TextInput';
|
||||
import { useUserProfile } from '../../context/UserProfileContext';
|
||||
import { IUserUpdateData } from '../../utils/models';
|
||||
import { ChangePassword } from './ChangePassword';
|
||||
|
||||
|
||||
export function UserProfile() {
|
||||
const { updateUser, user, processing } = useUserProfile();
|
||||
const { updateUser, user, processing, error } = useUserProfile();
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [first_name, setFirstName] = useState('');
|
||||
const [last_name, setLastName] = useState('');
|
||||
|
||||
|
||||
// const [showChangePassword, setShowChangePassword] = useState(false);
|
||||
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (user) {
|
||||
|
@ -35,8 +40,10 @@ export function UserProfile() {
|
|||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
||||
<div className='flex flex-col items-center justify-center px-2 py-2 border'>
|
||||
<div><h1 className='flex justify-center py-2'> Учетные данные пользователя </h1>
|
||||
<div className='flex flex-row justify-center py-2'>
|
||||
<div className='flex-grow max-w-sm px-4 '>
|
||||
<form onSubmit={handleSubmit} className='flex-grow px-6 py-2 border center min-w-fit'>
|
||||
<TextInput id='username'
|
||||
label='Логин:'
|
||||
value={username}
|
||||
|
@ -49,15 +56,18 @@ export function UserProfile() {
|
|||
/>
|
||||
<TextInput id='last_name' label="Фамилия:" value={last_name} onChange={event => setLastName(event.target.value)}/>
|
||||
<TextInput id='email' label="Электронная почта:" value={email} onChange={event => setEmail(event.target.value)}/>
|
||||
<div className='flex items-center justify-between my-4'>
|
||||
<div className='flex items-center justify-center my-4'>
|
||||
<button
|
||||
type='submit'
|
||||
className='px-2 py-1 bg-green-500 border'
|
||||
className='px-2 py-1 bg-green-500 border center'
|
||||
disabled={processing}>
|
||||
<span>Сохранить</span>
|
||||
<span>Сохранить мои данные</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<ChangePassword />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
|
@ -9,7 +9,7 @@ import {
|
|||
ICurrentUser, IExpressionParse, IRSExpression,
|
||||
IRSFormCreateData, IRSFormData,
|
||||
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
|
||||
IUserLoginData, IUserProfile, IUserSignupData, IUserUpdateData
|
||||
IUserLoginData, IUserProfile, IUserSignupData, IUserUpdateData, IUserUpdatePassword
|
||||
} from './models'
|
||||
|
||||
export function initBackend() {
|
||||
|
@ -101,6 +101,14 @@ export function patchProfile(request: FrontExchange<IUserUpdateData, IUserProfil
|
|||
});
|
||||
}
|
||||
|
||||
export function patchPassword(request: FrontPush<IUserUpdatePassword>) {
|
||||
AxiosPatch({
|
||||
title: 'Update Password',
|
||||
endpoint: '/users/api/change-password',
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function getActiveUsers(request: FrontPull<IUserInfo[]>) {
|
||||
AxiosGet({
|
||||
title: 'Active users list',
|
||||
|
|
|
@ -25,6 +25,11 @@ export interface IUserUpdateData extends Omit<IUser, 'is_staff' | 'id'> {}
|
|||
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||
|
||||
export interface IUserUpdatePassword {
|
||||
old_password: string
|
||||
new_password: string
|
||||
}
|
||||
|
||||
// ======== RS Parsing ============
|
||||
export enum Syntax {
|
||||
UNDEF = 'undefined',
|
||||
|
|
Loading…
Reference in New Issue
Block a user