diff --git a/backend/databases/user_manager.py b/backend/databases/user_manager.py
index 2ff741d..e5fd03b 100644
--- a/backend/databases/user_manager.py
+++ b/backend/databases/user_manager.py
@@ -175,3 +175,21 @@ def delete_user(self, user_id: int):
self.db_session.delete(db_user)
self.db_session.commit()
return db_user
+
+ def update_user_password(self, username: str, new_hashed_password: str):
+ """
+ Update a user's password in the database.
+
+ Args:
+ old_password (str): The user's current password.
+ new_password (str): The user's new password.
+
+ Returns:
+ User: The updated User object if found, else None.
+ """
+ db_user = self.db_session.query(User).filter(User.username == username).first()
+ if db_user:
+ db_user.hashed_password = new_hashed_password
+ self.db_session.commit()
+ self.db_session.refresh(db_user)
+ return db_user
diff --git a/backend/models/user.py b/backend/models/user.py
index a647934..21e2aca 100644
--- a/backend/models/user.py
+++ b/backend/models/user.py
@@ -1,9 +1,11 @@
from enum import Enum
-from pydantic import BaseModel, EmailStr
+from pydantic import BaseModel, EmailStr, Field, validator
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.sql import func
from typing import Optional
+import re
+
from .base import Base
@@ -95,3 +97,18 @@ class UserUpdate(BaseModel):
username: str
organization_id: Optional[int]
role: Optional[UserRole]
+
+
+class ChangePassword(BaseModel):
+ old_password: str = Field(...)
+ new_password: str = Field(...)
+
+ @validator("new_password")
+ def validate_new_password(cls, v):
+ if len(v) < 8:
+ raise ValueError("Password should have at least 8 characters")
+ if not re.search(r"\d", v):
+ raise ValueError("Password should contain at least one digit")
+ if not re.search(r"\W", v):
+ raise ValueError("Password should contain at least one symbol")
+ return v
diff --git a/backend/routes/user_routes.py b/backend/routes/user_routes.py
index cd9da2e..2b595cd 100644
--- a/backend/routes/user_routes.py
+++ b/backend/routes/user_routes.py
@@ -1,7 +1,12 @@
from fastapi import APIRouter, Depends, HTTPException
-from security import get_current_admin_user, get_current_user
+from security import (
+ get_current_admin_user,
+ get_current_user,
+ get_password_hash,
+ verify_password,
+)
-from models.user import User, UserOut, UserRole, UserUpdate
+from models.user import ChangePassword, User, UserOut, UserRole, UserUpdate
from databases.database_manager import DatabaseManager
from databases.user_manager import UserManager
@@ -46,3 +51,31 @@ async def update_user(
raise HTTPException(status_code=404, detail="User not found")
return {"message": f"Successfully updated details for {user_data.username}."}
+
+
+@user_router.put("/users/change-password/")
+async def change_user_password(
+ change_password: ChangePassword, current_user: User = Depends(get_current_user)
+):
+ print("change_password", change_password)
+ # Verify old password
+ if not verify_password(change_password.old_password, current_user.hashed_password):
+ print("HELLO")
+ raise HTTPException(status_code=400, detail="Invalid old password")
+
+ with DatabaseManager() as session:
+ user_manager = UserManager(session)
+
+ # Update user password
+ new_hashed_password = get_password_hash(change_password.new_password)
+ updated_user = user_manager.update_user_password(
+ username=current_user.username,
+ new_hashed_password=new_hashed_password,
+ )
+
+ if not updated_user:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ return {
+ "message": f"Successfully updated password for {updated_user.username}."
+ }
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index c58ecf1..479be4e 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -27,7 +27,7 @@ import LoginPage from './pages/Login';
import PricingPage from './pages/Pricing';
import RegisterPage from './pages/Register';
import UploadPage from './pages/upload/Upload';
-import UserPage from './pages/User';
+import UserPage from './pages/user/UserPage';
import Logout from './pages/Logout';
function AppWrapper() {
diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx
index b170e94..1c96213 100644
--- a/frontend/src/pages/Login.jsx
+++ b/frontend/src/pages/Login.jsx
@@ -22,7 +22,6 @@ function LoginPage({ onLogin }) {
// Determine if usernameOrEmail should be sent as username or email
const isEmail = validator.isEmail(usernameOrEmail);
- console.log(isEmail)
const data = isEmail ? { email: usernameOrEmail, password } : { username: usernameOrEmail, password };
try {
diff --git a/frontend/src/pages/User.jsx b/frontend/src/pages/User.jsx
deleted file mode 100644
index df4d1b6..0000000
--- a/frontend/src/pages/User.jsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import axios from 'axios';
-import Typography from '@mui/material/Typography';
-import Box from '@mui/material/Box';
-import Grid from '@mui/material/Grid';
-import Card from '@mui/material/Card';
-import CardContent from '@mui/material/CardContent';
-import CircularProgress from '@mui/material/CircularProgress';
-import Alert from '@mui/material/Alert';
-import EmailIcon from '@mui/icons-material/Email';
-import BusinessIcon from '@mui/icons-material/Business';
-import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
-import AccountCircleIcon from '@mui/icons-material/AccountCircle';
-import { API_URL } from '../utils/constants';
-
-const UserPage = () => {
- const [userData, setUserData] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
- const [organizationData, setOrganizationData] = useState(null);
-
- useEffect(() => {
- setIsLoading(true);
- const fetchData = async () => {
- try {
- const userResponse = await axios.get(`${API_URL}users/me/`);
- const userData = userResponse.data;
- setUserData(userData);
-
- // Fetch organization data if organization_id is present
- if (userData.organization_id) {
- const orgResponse = await axios.get(`${API_URL}organization/`, {
- params: { org_id: userData.organization_id }
- });
- setOrganizationData(orgResponse.data);
- }
- } catch (error) {
- setError(error.response ? error.response.data.message : error.message);
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, []);
-
- return (
-
-
- 👤 User Panel
-
-
- {isLoading ? (
-
- ) : error ? (
- {error}
- ) : userData ? (
-
- {/* Username Card */}
-
-
-
-
- Username
- {userData.username}
-
-
-
-
- {/* Email Card */}
-
-
-
-
- Email
- {userData.email}
-
-
-
-
- {/* Organization Card */}
-
-
-
-
- Organization
- {organizationData.name}
-
-
-
-
- {/* Role Card */}
-
-
-
-
- Role
- {userData.role}
-
-
-
- {/* Additional Cards or Components */}
-
- ) : (
- User details not available.
- )}
-
- );
-};
-
-export default UserPage;
diff --git a/frontend/src/pages/user/ChangePassword.jsx b/frontend/src/pages/user/ChangePassword.jsx
new file mode 100644
index 0000000..86a5418
--- /dev/null
+++ b/frontend/src/pages/user/ChangePassword.jsx
@@ -0,0 +1,88 @@
+import React, { useState } from 'react';
+import { Alert, Box, Button, Grid, Paper, Snackbar, TextField, Typography } from '@mui/material';
+
+const ChangePassword = ({ handleChangePassword, errorMessage, successMessage }) => {
+ const [oldPassword, setOldPassword] = useState('');
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+ const success = await handleChangePassword(oldPassword, newPassword, confirmPassword);
+ if (success) {
+ setOldPassword('');
+ setNewPassword('');
+ setConfirmPassword('');
+ }
+ };
+
+ return (
+
+
+
+ Change Password
+ setOldPassword(e.target.value)}
+ />
+ setNewPassword(e.target.value)}
+ />
+ setConfirmPassword(e.target.value)}
+ />
+ {successMessage &&
+
+ {successMessage}
+
+ }
+ {errorMessage &&
+
+ {errorMessage}
+
+ }
+
+
+
+
+ );
+};
+
+export default ChangePassword;
\ No newline at end of file
diff --git a/frontend/src/pages/user/InfoCard.jsx b/frontend/src/pages/user/InfoCard.jsx
new file mode 100644
index 0000000..aebf828
--- /dev/null
+++ b/frontend/src/pages/user/InfoCard.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Grid from '@mui/material/Grid';
+import Typography from '@mui/material/Typography';
+
+const InfoCard = ({ Icon, title, content }) => (
+
+
+
+
+ {title}
+ {content}
+
+
+
+);
+
+export default InfoCard;
\ No newline at end of file
diff --git a/frontend/src/pages/user/UserPage.jsx b/frontend/src/pages/user/UserPage.jsx
new file mode 100644
index 0000000..9188274
--- /dev/null
+++ b/frontend/src/pages/user/UserPage.jsx
@@ -0,0 +1,110 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import EmailIcon from '@mui/icons-material/Email';
+import BusinessIcon from '@mui/icons-material/Business';
+import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
+import AccountCircleIcon from '@mui/icons-material/AccountCircle';
+import { Alert, Box, CircularProgress, Grid, Snackbar, Typography } from '@mui/material';
+import InfoCard from './InfoCard';
+import ChangePassword from './ChangePassword';
+import { API_URL } from '../../utils/constants';
+
+const UserPage = () => {
+ const [userData, setUserData] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [organizationData, setOrganizationData] = useState(null);
+ const [errorMessage, setErrorMessage] = useState('');
+ const [successMessage, setSuccessMessage] = useState('');
+
+ useEffect(() => {
+ setIsLoading(true);
+ const fetchData = async () => {
+ try {
+ const userResponse = await axios.get(`${API_URL}users/me/`);
+ const userData = userResponse.data;
+ setUserData(userData);
+
+ // Fetch organization data if organization_id is present
+ if (userData.organization_id) {
+ const orgResponse = await axios.get(`${API_URL}organization/`, {
+ params: { org_id: userData.organization_id }
+ });
+ setOrganizationData(orgResponse.data);
+ }
+ } catch (error) {
+ setError(error.response ? error.response.data.message : error.message);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ const handleChangePassword = async (oldPassword, newPassword, confirmPassword) => {
+ if (newPassword !== confirmPassword) {
+ setErrorMessage('Passwords do not match!');
+ return;
+ }
+
+ try {
+ const response = await axios.put(`${API_URL}users/change-password/`, { old_password: oldPassword, new_password: newPassword });
+
+ if (response.status === 200) {
+ setSuccessMessage('Password changed successfully');
+ setErrorMessage('');
+ return true;
+ } else {
+ setErrorMessage('Failed to change password!');
+ return false;
+ }
+ } catch (error) {
+ if (error.response) {
+ if (error.response.status === 400) {
+ setErrorMessage(error.response.data.detail);
+ } else if (error.response.data && error.response.data.detail && error.response.data.detail[0]) {
+ let errorMessage = error.response.data.detail[0].msg;
+ if (typeof errorMessage === 'string') {
+ errorMessage = errorMessage.replace('Value error, ', '');
+ setErrorMessage(errorMessage);
+ }
+ }
+ } else if (error.request) {
+ // The request was made but no response was received
+ console.log(error.request);
+ } else {
+ // Something happened in setting up the request that triggered an Error
+ console.log('Error', error.message)
+ }
+ return false;
+ }
+ };
+
+ return (
+
+
+ 👤 User Panel
+
+
+ {isLoading ? (
+
+ ) : error ? (
+ {error}
+ ) : userData ? (
+
+
+
+
+
+
+ ) : (
+ User details not available.
+ )}
+
+
+
+ );
+};
+
+export default UserPage;