Skip to content

Commit

Permalink
create change password functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
liberty-rising committed Dec 24, 2023
1 parent b706c2d commit f73284d
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 116 deletions.
18 changes: 18 additions & 0 deletions backend/databases/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 18 additions & 1 deletion backend/models/user.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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
37 changes: 35 additions & 2 deletions backend/routes/user_routes.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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}."
}
2 changes: 1 addition & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
111 changes: 0 additions & 111 deletions frontend/src/pages/User.jsx

This file was deleted.

88 changes: 88 additions & 0 deletions frontend/src/pages/user/ChangePassword.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Grid item xs={12}>
<Paper elevation={3}>
<Box p={3}>
<Typography variant="h6">Change Password</Typography>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="oldPassword"
label="Old Password"
type="password"
id="oldPassword"
autoComplete="current-password"
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="newPassword"
label="New Password"
type="password"
id="newPassword"
autoComplete="current-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="confirmPassword"
label="Confirm New Password"
type="password"
id="confirmPassword"
autoComplete="current-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
{successMessage &&
<Box mt={2} mb={2}>
<Alert severity="success">{successMessage}</Alert>
</Box>
}
{errorMessage &&
<Box mt={2} mb={2}>
<Alert severity="error">{errorMessage}</Alert>
</Box>
}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={handleSubmit}
>
Change Password
</Button>
</Box>
</Paper>
</Grid>
);
};

export default ChangePassword;
19 changes: 19 additions & 0 deletions frontend/src/pages/user/InfoCard.jsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<Grid item xs={12} sm={6} md={4}>
<Card>
<CardContent>
<Icon />
<Typography variant="h6">{title}</Typography>
<Typography variant="body1">{content}</Typography>
</CardContent>
</Card>
</Grid>
);

export default InfoCard;
Loading

0 comments on commit f73284d

Please sign in to comment.