Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create change password functionality #133

Merged
merged 1 commit into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading